#version 330 core
out vec4 FragColor;

struct PRS_RES {
	vec3 FragPos;
	vec2 TexCoord;
	vec3 Normal;
	vec3 Color;
	vec4 FragPosLgtVP;
};

in PRS_RES gs_res;

//in vec3 vsColor;
//in vec2 vsTexCoord;

//in vec3 vsFragPos;  // World space location of the fragment vs gl_FragCoord
//in vec3 vsNormal;

// uniform for screen dimensions (integer numbers)
uniform ivec2 screenSizePix;

// uniform for time delta
uniform float timeDelta;

// uniforms for lighting
uniform vec3 viewPos;

// rendering type
uniform int renderingType;
uniform int objectType;
uniform int light_count;

uniform float tex_scale;

#define MAX_LIGHTS 16

// light source properties
struct Light {
    int  type;  // 0 ambient, 1 directional, 2 point, 3 spot, 4 point_atn, 5 spot_atn
    vec3 direction;
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
	
    float atn_cnst;
    float atn_lin;
    float atn_quad;
    
    float cutoffInner;
    float cutOffSt;
    float cutOff;
    float FOV;
};

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    vec3 shinyness;

    sampler2D diffTex;
    sampler2D ambtTex;
    sampler2D specTex;
    sampler2D normTex;
};

uniform Light light[MAX_LIGHTS];
uniform Material mtrl[2];

uniform bool isBlinnPhong;

uniform sampler2D shadowMap;
uniform samplerCube shadowMapCube[4];
uniform int cubeIndex;

uniform samplerCube skyboxTex;  // Texture from the skybox

void calc_ambient( out vec3 ambient_out, vec3 light, vec3 material ){
    ambient_out = light * material;
}

void calc_diffuse( out vec3 diffuse_out, out float diff_out, vec3 normal, vec3 pixel_pos, vec3 light_dir, vec3 light, vec3 material ){
    diff_out     = max( dot( normal, -light_dir ), 0.0 );
    diffuse_out  = light * diff_out * material;
}

void calc_specular( out vec3 specular_out, vec3 normal, vec3 view_dir, vec3 light_dir, vec3 light, vec3 material, float shiny ){
    float viewedReflect;
    if( isBlinnPhong ){
        vec3 halfwayDir = normalize( view_dir - light_dir );  
        viewedReflect   = max( dot( normal, halfwayDir ), 0.0 );
    } else {
        vec3 reflectDir = reflect( light_dir, normal );
        viewedReflect   = max( dot( view_dir, reflectDir ), 0.0 );
    }
    float spec          = pow( viewedReflect, shiny );
    specular_out        = light * spec * material;
}

void calc_tbn_matrix( out mat3 tbn, vec3 normal, vec3 pixel_pos, vec2 pixel_texcoord ){
    // Calculate derivatives
    vec3 pos_dx  = dFdx(pixel_pos);
    vec3 pos_dy  = dFdy(pixel_pos);
    vec2 texC_dx = dFdx(pixel_texcoord);
    vec2 texC_dy = dFdy(pixel_texcoord);

    // Calculate tangent and bitangent
    vec3 tangent   = normalize(texC_dy.y * pos_dx - texC_dx.y * pos_dy);
    vec3 bitangent = normalize(texC_dx.x * pos_dy - texC_dy.x * pos_dx);

    // Construct TBN matrix
    tbn = mat3( tangent, bitangent, normal ); // normal interpolated vertex normal (after normalization)
}

void calc_light_dir_and_atn( out vec3 lightDir, out float atn_amb, out float atn_ds, Light light ){
    lightDir = vec3( 0.0, -1.0, 0.0 );
    atn_amb  = 1.0f;
    atn_ds   = 1.0f;

    float amb_intensity  = 1.0f;
    float spot_intensity = 1.0f;

    if( light.type == 0 ){
        atn_ds   = 0.0f;
    } else if( light.type == 1 ){
        lightDir = light.direction;
        lightDir = normalize( lightDir );
    } else {
        lightDir = gs_res.FragPos - light.position;
        vec3 lightDir_norm = normalize( lightDir );
        if( light.type > 3 ){
            // distance based attenuation
            float distance = length( lightDir );
            atn_amb        = 1.0 / ( light.atn_cnst + ( light.atn_lin + ( light.atn_quad * distance ) * distance ) );
            atn_ds         = atn_amb;
        }

        if( light.type == 3 || light.type == 5 ){
            // spotlight (soft edges)
            // The lightDir_norm is the position relative to the vsFragPos while the light[0].direction is the pointing direction
            float theta    = dot( -lightDir_norm, normalize( -light.direction ) );
            spot_intensity = clamp( ( theta - light.cutOff ) / ( light.cutOffSt - light.cutOff ), 0.0, 1.0 );
            amb_intensity  = clamp( ( theta - light.FOV    ) / ( light.cutOffSt - light.FOV    ), 0.0, 1.0 );
            atn_ds  *= spot_intensity;
            atn_amb *= amb_intensity;
        }
        lightDir = lightDir_norm;
    }
}

float ShadowCalculation( vec4 fragPosSrcLgtVP, vec3 lightDir, vec3 lightPos, vec3 normal ){
    // perform perspective divide
    vec3 projCoords = fragPosSrcLgtVP.xyz / fragPosSrcLgtVP.w;
    // transform to [0,1] range
    projCoords = projCoords * 0.5 + 0.5;

    float shadow = 0.0;  // keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
    if( projCoords.z <= 1.0 ){
        // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
        float closestDepth = texture(shadowMap, projCoords.xy).r; 
        // get depth of current fragment from light's perspective
        float currentDepth = projCoords.z;
        
        float bias = 0.002;
        float cdmb = (currentDepth - bias);
        //float bias = max(0.02 * (1.0 - dot(normal, -lightDir)), 0.002);
        // check whether current frag pos is in shadow
        shadow = cdmb > closestDepth  ? 1.0 : 0.0;
        
        // PCF
        /*vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
        for( int x = -1; x <= 1; ++x ){
            for( int y = -1; y <= 1; ++y ){
                float pcfDepth = texture( shadowMap, projCoords.xy + vec2(x, y) * texelSize ).r; 
                shadow += cdmb > pcfDepth  ? 1.0 : 0.0;        
            }    
        }
        shadow /= 9.0;*/
    }
    
    return shadow;
}

float ShadowCalculationCubemap( vec3 fragPos, vec3 lightPos, int lgt ){
    // get vector between fragment position and light position
    vec3 fragToLight = fragPos - lightPos;
    // use the light to fragment vector to sample from the depth map
    float closestDepth = texture( shadowMapCube[lgt], fragToLight ).r;
    // it is currently in linear range between [0,1]. Re-transform back to original value
    //closestDepth *= far_plane;
    closestDepth *= 100.0;
    // now get current linear depth as the length between the fragment and light position
    float currentDepth = length( fragToLight );
    // now test for shadows
    float bias = 0.005;
    float shadow = (currentDepth - bias) > closestDepth ? 1.0 : 0.0;
    //float shadow = currentDepth > closestDepth ? 1.0 : 0.0;

    return shadow;
}

void main(){
    vec2 texCoord = gs_res.TexCoord * tex_scale;
    if( objectType == 0 ){
        if( renderingType < 12 ){
            FragColor = vec4( light[0].diffuse, 1.0 );
        } else {
            FragColor = vec4( light[light_count].diffuse, 1.0 );
        }
    } else {
        // Implementation rendering a depth map texture (first channel only for all the channels)
        if( renderingType == 0 ){
            float near  = 0.1; 
            float far   = 100.0; 
            float z     = texture( mtrl[0].diffTex, texCoord ).r * 2.0 - 1.0; // back to NDC 
            float depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
            FragColor = vec4( vec3( depth ), 1.0 );
            //FragColor = vec4( depthColor, depthColor, depthColor, 1.0 );
        } else if( renderingType == 1 ){
            // First implementation of the example using only the first texture
            FragColor = texture( mtrl[0].diffTex, texCoord );
        } else if( renderingType == 2 ){
            // Second implementation of the example using texture and color inputs
            FragColor = texture( mtrl[0].diffTex, texCoord ) * vec4( gs_res.Color, 1.0 );
        } else if( renderingType == 3 ){
            // Third implmentation of the example linearly interpolating between both textures
            FragColor = mix( texture( mtrl[0].diffTex, texCoord ), texture( mtrl[1].diffTex, texCoord ), 0.5 );
        } else if( renderingType == 4 ){
            // Fourth implmentation of the example with colored object defined by vec3 and a light
            // It's not really "diffuse" lighting, diffuse is just the default for "what color is this light?"
            FragColor = vec4( light[0].diffuse * mtrl[0].diffuse, 1.0 );
        } else if( renderingType == 5 ){
            // Fifth implementation of the example with only ambient lighting
            vec3 ambient;
            calc_ambient( ambient, light[0].ambient, mtrl[0].ambient );
            FragColor    = vec4( ambient, 1.0 );
        } else {
            // Implementations after this are going to use the direction the light is pointing relative to the fragment
            vec3 lightDir, ambient, diffuse, specular;
            float diff,    atn_amb, atn_ds;

            vec3 normal_norm = normalize( gs_res.Normal );
            vec3 viewDir     = normalize( viewPos - gs_res.FragPos );

            calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, light[0] );
            if( renderingType < 11 ){
                float shadow = ShadowCalculation( gs_res.FragPosLgtVP, lightDir, light[0].position, normal_norm );
                atn_ds *= ( 1.0 - shadow );
            }

            if( renderingType == 6 ){
                // Sixth implementation of the example with ambient and diffuse lighting
                calc_ambient( ambient, light[0].ambient, mtrl[0].ambient );
                calc_diffuse( diffuse, diff, normal_norm, gs_res.FragPos, lightDir, light[0].diffuse, mtrl[0].diffuse );
                FragColor     = vec4( ambient * atn_amb + diffuse * atn_ds, 1.0 );
                //FragColor     = vec4( gs_res.Normal, 1.0 );
            } else if( renderingType == 7 ){
                // Seventh implementation of the example with ambient, diffuse, and specular lighting (Phong Lighting)
                calc_ambient( ambient, light[0].ambient, mtrl[0].ambient );
                calc_diffuse( diffuse, diff, normal_norm, gs_res.FragPos, lightDir, light[0].diffuse, mtrl[0].diffuse );
                calc_specular( specular, normal_norm, viewDir, lightDir, light[0].specular, mtrl[0].specular, mtrl[0].shinyness.x );
                FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
            } else if( renderingType == 8 ){
                // Eighth implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), and textures
                // blend all the textures
                vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                calc_ambient( ambient, light[0].ambient, (mtrl[0].ambient * ambtTex.r) );
                calc_diffuse( diffuse, diff, normal_norm, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex) );
                calc_specular( specular, normal_norm, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex), mtrl[0].shinyness.x );
                    
                FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
            } else if( renderingType == 9 ){
                // Nineth implementation of the example for ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );

                vec3 ambtTex = texture( mtrl[0].ambtTex, texCoord ).rgb;
                vec3 diffTex = texture( mtrl[0].diffTex, texCoord ).rgb;
                vec3 specTex = texture( mtrl[0].specTex, texCoord ).rgb;

                calc_ambient( ambient, light[0].ambient, (mtrl[0].ambient * ambtTex.r) );
                calc_diffuse( diffuse, diff, worldNormal1, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex) );
                calc_specular( specular, worldNormal1, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex.r), mtrl[0].shinyness.x );
                    
                FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
            } else if( renderingType == 10 ){
                // Tenth implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                // This implementation (vs 11 below) takes two textures, blends them together, and then uses blended normals and textures
                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );
                vec3 normalMapNormal2 = normalize( texture( mtrl[1].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );
                vec3 worldNormal2 = normalize( tbn * normalMapNormal2 );

                vec3 pixelNormal = normalize( mix( worldNormal1, worldNormal2, 0.5 ) );

                // blending two textures together and using an average worldNormal based on both at the pixel (pixelNormal)
                vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                calc_ambient( ambient, light[0].ambient, (mtrl[0].ambient * ambtTex.r) );
                calc_diffuse( diffuse, diff, pixelNormal, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex) );
                calc_specular( specular, pixelNormal, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex.r), mtrl[0].shinyness.x );
                    
                FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
            } else if( renderingType == 11 ){
                // Eleventh implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                // This implementation handles diffuse and specular seperately for each texture
                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );
                vec3 normalMapNormal2 = normalize( texture( mtrl[1].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );
                vec3 worldNormal2 = normalize( tbn * normalMapNormal2 );

                // blend the textures for ambient, diffuse and specular handle each texture independently
                vec3 ambtTex  = mix( texture( mtrl[0].ambtTex,  texCoord ).rgb, texture( mtrl[1].ambtTex,  texCoord ).rgb, 0.5 );
                vec3 diffTex1 = texture( mtrl[0].diffTex, texCoord ).rgb;
                vec3 diffTex2 = texture( mtrl[1].diffTex, texCoord ).rgb;
                vec3 specTex1 = texture( mtrl[0].specTex, texCoord ).rgb;
                vec3 specTex2 = texture( mtrl[1].specTex, texCoord ).rgb;

                vec3 ambient, diffuse1, diffuse2, specular1, specular2;
                float diff1, diff2;
                
                calc_ambient( ambient, light[0].ambient, (mtrl[0].ambient * ambtTex.r) );
                calc_diffuse( diffuse1, diff1, worldNormal1, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex1) );
                calc_diffuse( diffuse2, diff2, worldNormal2, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex2) );
                calc_specular( specular1, worldNormal1, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex1.r), mtrl[0].shinyness.x );
                calc_specular( specular2, worldNormal2, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex2.r), mtrl[0].shinyness.x );

                FragColor = vec4( ambient * atn_amb + ( mix( diffuse1, diffuse2, 0.5 ) + mix( specular1, specular2, 0.5 ) ) * atn_ds, 1.0 );
            } else if( renderingType == 12 ){
                // Twelfth implementation of the example with multiple light sources
                // Lights and surfaces have ambient, diffuse, specular lighting (Phong Lighting), and textures
                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );

                vec3 ambtTex = texture( mtrl[0].ambtTex, texCoord ).rgb;
                vec3 diffTex = texture( mtrl[0].diffTex, texCoord ).rgb;
                vec3 specTex = texture( mtrl[0].specTex, texCoord ).rgb;

                vec4 result = vec4( 0.0, 0.0, 0.0, 0.0 );

                int light_st, light_end;
                if( cubeIndex == 0 ){
                    light_st = 1; light_end = light_count;
                } else if( cubeIndex == 1 ){
                    light_st = 2; light_end = light_st+1;
                } else if( cubeIndex == 2 ){
                    light_st = 3; light_end = light_st+1;
                } else if( cubeIndex == 3 ){
                    light_st = 4; light_end = light_st+1;
                } else if( cubeIndex == 4 ){
                    light_st = 5; light_end = light_st+1;
                }

                for( int i = light_st; i < light_end; i++ ){
                    calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, light[i] );
                    int ptLgt = i - 2;
                    if( ptLgt >= 0 && ptLgt < 4 ){
                        float shadow = ShadowCalculationCubemap( gs_res.FragPos, light[i].position, ptLgt );
                        atn_ds *= ( 1.0 - shadow );
                    }
                    calc_ambient( ambient, light[i].ambient, (mtrl[0].ambient * ambtTex.r) );
                    diffuse  = vec3( 0.0, 0.0, 0.0 );
                    specular = vec3( 0.0, 0.0, 0.0 );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, diff, worldNormal1, gs_res.FragPos, lightDir, light[i].diffuse, (mtrl[0].diffuse * diffTex) );
                        calc_specular( specular, worldNormal1, viewDir, lightDir, light[i].specular, (mtrl[0].specular * specTex.r), mtrl[0].shinyness.x );
                    }
                        
                    result += vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                }
                FragColor = result;
            } else if( renderingType == 13 ){
                // Thirteenth implementation that renders the depth of the pixels on screen
                // Normal depth used by the shader is only gl_FragCoord.z
                // Linearized depth is (2.0 * near ) / (far + near - z * (far - near))
                // This implementation then takes the sqrt to accentuate those values 
                float near  = 0.1; 
                float far   = 100.0; 
                float z     = gl_FragCoord.z * 2.0 - 1.0; // back to NDC 
                float depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
                FragColor = vec4( vec3( depth ), 1.0 );
            } else if( renderingType == 14 ){
                // Fourteenth implementation that renders smooth reflection on surfaces from the skybox cubemap
                // Similar to the specular code, except that this takes the view direction, reflects it
                // and then samples from the cubemap texture
                vec3 reflectDir = reflect( -viewDir, normal_norm );
                FragColor       = vec4( texture( skyboxTex, reflectDir ).rgb, 1.0 );
            } else if( renderingType == 15 ){
                // Fifteenth implementation that renders reflections from the cubemap with normal maps applied
                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal = normalize( tbn * normalMapNormal );
                vec3 reflectDir  = reflect( -viewDir, worldNormal );
                FragColor        = vec4( texture( skyboxTex, reflectDir ).rgb, 1.0 );
            } else if( renderingType == 16 ){
                // Sixteenth implementation that renders smooth refraction thru surfaces from the skybox cubemap
                // Very similar to fourteen except it uses the refract function vs the reflect function
                // Implements only single layer refraction rather than multi-layer
                float snell_ratio = 1.00 / 1.52;  // air to glass ratio
                vec3 refractDir = refract( -viewDir, normal_norm, snell_ratio );
                FragColor       = vec4( texture( skyboxTex, refractDir ).rgb, 1.0 );
            } else if( renderingType == 17 ){
                // Seventeenth implementation that renders refraction thru surfaces with a normal map
                float snell_ratio = 1.00 / 1.52;  // air to glass ratio

                // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                // Construct TBN matrix
                mat3 tbn;
                calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                // Transform the normal
                vec3 worldNormal = normalize( tbn * normalMapNormal );
                vec3 refractDir = refract( -viewDir, worldNormal, snell_ratio );
                FragColor       = vec4( texture( skyboxTex, refractDir ).rgb, 1.0 );
            } else if( renderingType == 18 ){
                // Eighteenth implementation that renders multiple window views
                // Calculate an angle for the view
                float timeDeltaMlt = timeDelta * 27.9120741831915;

                ivec2 screenSized2 = screenSizePix / 2;
                float viewAngle = atan( ( gl_FragCoord.y - screenSized2.y ), ( gl_FragCoord.x - screenSized2.x ) );

                if( ( viewAngle < -1.247197551 + timeDeltaMlt ) || ( viewAngle > 2.941592654 + timeDeltaMlt ) ){
                    // blend all the textures
                    vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                    vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                    vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                    calc_ambient( ambient, light[0].ambient, (mtrl[0].ambient * ambtTex.r) );
                    calc_diffuse( diffuse, diff, normal_norm, gs_res.FragPos, lightDir, light[0].diffuse, (mtrl[0].diffuse * diffTex) );
                    calc_specular( specular, normal_norm, viewDir, lightDir, light[0].specular, (mtrl[0].specular * specTex), mtrl[0].shinyness.x );
                        
                    if( gl_FrontFacing ){
                        FragColor = vec4( 1.0, 0.2, 0.2, 1.0 ) * vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                    } else {
                        FragColor = vec4( 0.2, 1.0, 1.0, 1.0 ) * vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                    }
                } else if( ( viewAngle > 0.847197551 + timeDeltaMlt ) && ( viewAngle < 2.941592654 + timeDeltaMlt ) ){
                    float snell_ratio = 1.00 / 1.52;  // air to glass ratio

                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, gs_res.FragPos, texCoord );

                    // Transform the normal
                    vec3 worldNormal = normalize( tbn * normalMapNormal );
                    vec3 refractDir = refract( -viewDir, worldNormal, snell_ratio );
                    
                    if( gl_FrontFacing ){
                        FragColor       = vec4( 0.2, 1.0, 0.2, 1.0 ) * vec4( texture( skyboxTex, refractDir ).rgb, 1.0 );
                    } else {
                        FragColor       = vec4( 1.0, 0.2, 1.0, 1.0 ) * vec4( texture( skyboxTex, refractDir ).rgb, 1.0 );
                    }
                } else { 
                    float near  = 0.1; 
                    float far   = 100.0; 
                    float z     = gl_FragCoord.z * 2.0 - 1.0; // back to NDC 
                    float depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
                    
                    if( gl_FrontFacing ){
                        FragColor = vec4( 0.2, 0.2, 1.0, 1.0 ) * vec4( vec3( depth ), 1.0 );
                    } else {
                        FragColor = vec4( 1.0, 1.0, 0.2, 1.0 ) * vec4( vec3( depth ), 1.0 );
                    }
                }
            }
        }
    }
}