#include "/lib/settings.glsl"

#include "/lib/SSBOs.glsl"

#ifdef OVERWORLD_SHADER
  in DATA {
    flat vec3 WsunVec;
    flat vec3 WmoonVec;
  };
#endif

#define DEFERRED_SPECULAR
#define DEFERRED_SSR_QUALITY 15 // [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 200 300 400 500]

uniform sampler2D noisetex;
uniform sampler2D depthtex0;
uniform sampler2D depthtex1;

#ifdef DISTANT_HORIZONS
	uniform sampler2D dhDepthTex;
	uniform sampler2D dhDepthTex1;
	#define dhVoxyDepthTex dhDepthTex
	#define dhVoxyDepthTex1 dhDepthTex1
#endif

#ifdef VOXY
	uniform sampler2D vxDepthTexOpaque;
	uniform sampler2D vxDepthTexTrans;
	#define dhVoxyDepthTex vxDepthTexTrans
	#define dhVoxyDepthTex1 vxDepthTexOpaque
#endif

uniform sampler2D colortex0;
uniform sampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex4;
uniform sampler2D colortex5;
uniform sampler2D colortex6;
uniform sampler2D colortex7;
uniform sampler2D colortex8;
uniform sampler2D colortex9;
uniform sampler2D colortex10;
uniform sampler2D colortex11;
uniform sampler2D colortex12;
uniform sampler2D colortex13;
uniform sampler2D colortex14;
uniform sampler2D colortex15;
uniform vec2 texelSize;

uniform float viewHeight;
uniform float viewWidth;
uniform float nightVision;
uniform float fogEnd;
uniform vec3 fogColor;
uniform vec3 sunVec;
uniform float frameTimeCounter;
uniform int frameCounter;
uniform float far;
uniform float near;
uniform float farPlane;
uniform float dhVoxyNearPlane;
uniform float dhVoxyFarPlane;

uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferModelView;
uniform mat4 gbufferPreviousModelView;
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferProjection;
uniform mat4 gbufferPreviousProjection;
uniform vec3 cameraPosition;
uniform vec3 previousCameraPosition;

uniform int hideGUI;
uniform int dhVoxyRenderDistance;
uniform int isEyeInWater;
uniform ivec2 eyeBrightnessSmooth;
uniform ivec2 eyeBrightness;
uniform float rainStrength;
uniform float blindness;
uniform float darknessFactor;
uniform float darknessLightFactor;
uniform float caveDetection;
uniform float sunElevation;

#include "/lib/res_params.glsl"

#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)
#define  projMAD(m, v) (diagonal3(m) * (v) + (m)[3].xyz)
vec3 toScreenSpace(vec3 p) {
	vec4 iProjDiag = vec4(gbufferProjectionInverse[0].x, gbufferProjectionInverse[1].y, gbufferProjectionInverse[2].zw);
    vec3 p3 = p * 2. - 1.;
    vec4 fragposition = iProjDiag * p3.xyzz + gbufferProjectionInverse[3];
    return fragposition.xyz / fragposition.w;
}

float linearize(float dist) {
  return (2.0 * near) / (far + near - dist * (far - near));
}

vec3 toClipSpace3Prev(vec3 viewSpacePosition) {
    return projMAD(gbufferPreviousProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}

vec3 bilateralUpsample2(vec2 fragcoord, sampler2D colortex, out float outerEdgeResults, float referenceDepth, sampler2D depth, bool hand){

  vec3 colorSum = vec3(0.0);
  float edgeSum = 0.0;

  const float threshold = 0.005;


  const int samples = 5;
  
  vec2 coord = fragcoord - 1.5;

  vec2 UV = coord;
  const ivec2 SCALE = ivec2(1.0);
  ivec2 UV_COLOR = ivec2(UV);
  ivec2 UV_NOISE = ivec2(gl_FragCoord.xy*texelSize + 1);

	const ivec2 OFFSET[5] = ivec2[5](
    ivec2(-1,-1),
	 	ivec2( 1, 1),
		ivec2(-1, 1),
		ivec2( 1,-1),
		ivec2( 0, 0)
    // ,ivec2( 0, 1),
    // ivec2( 0,-1),
    // ivec2( 1, 0),
    // ivec2(-1, 0)
  );

  for(int i = 0; i < samples; i++) {
    float offsetDepth = linearize(texelFetch(depth, UV_COLOR + (OFFSET[i] + UV_NOISE), 0).r);

    float edgeDiff = abs(offsetDepth - referenceDepth) < threshold ? 1.0 : 1e-7;
    outerEdgeResults = max(outerEdgeResults, abs(referenceDepth - offsetDepth));

    vec3 offsetColor = texelFetch(colortex, UV_COLOR + OFFSET[i] + UV_NOISE, 0).rgb;
    colorSum += offsetColor*edgeDiff;
    edgeSum += edgeDiff;

  }

  outerEdgeResults = outerEdgeResults > (hand ? 0.005 : referenceDepth*0.05 + 0.1) ? 1.0 : 0.0;
  
  return colorSum / edgeSum;
}

vec3 VLTemporalFiltering2(vec2 texcoord, vec3 playerPosIn, in float referenceDepth, sampler2D depth, bool hand, vec3 currentFrame, float roughness){  
	// get previous frames position stuff for UV
	vec3 playerPos = playerPosIn + (cameraPosition - previousCameraPosition);
	vec3 previousPosition = mat3(gbufferPreviousModelView) * playerPos + gbufferPreviousModelView[3].xyz;
	previousPosition = toClipSpace3Prev(previousPosition);

	vec2 velocity = previousPosition.xy - texcoord;
	previousPosition.xy = texcoord + velocity;

  // return vec4(outerEdgeResults,0,0,1);
  // return upsampledCurrentFrame;

  if (previousPosition.x < 0.0 || previousPosition.y < 0.0 || previousPosition.x > 1.0 || previousPosition.y > 1.0 || hand) return currentFrame.rgb;

  // to fill pixel gaps in geometry edges, do a bilateral upsample.
  // pass a mask to only show upsampled color around the edges of blocks. this is so it doesnt blur reprojected results.
  float outerEdgeResults = 0.0;
  vec3 upsampledCurrentFrame = bilateralUpsample2(gl_FragCoord.xy , colortex0, outerEdgeResults, referenceDepth, depth, hand);
  //return currentFrame;
  // vec4 upsampledCurrentFrame = BilateralUpscale(colortex0, depth, gl_FragCoord.xy - 1.5, referenceDepth);
  // /*

  vec4 frameHistoryTex = texture(colortex12, previousPosition.xy*RENDER_SCALE);
  float prevDepth = frameHistoryTex.a;
  float linPrevDepth = linearize(previousPosition.z);

  vec3 frameHistory = frameHistoryTex.rgb;

  vec3 reprojectFrame = currentFrame.rgb;

  if(!(frameHistory.r == 0.0 && frameHistory.g == 0.0 && frameHistory.b == 0.0) && (abs(prevDepth-linPrevDepth) < max(linPrevDepth * 0.02, 0.001))) {
    vec3 col1 = texture(colortex0, texcoord + vec2( texelSize.x,  texelSize.y)).rgb;
    vec3 col2 = texture(colortex0, texcoord + vec2( texelSize.x, -texelSize.y)).rgb;
    vec3 col3 = texture(colortex0, texcoord + vec2(-texelSize.x, -texelSize.y)).rgb;
    vec3 col4 = texture(colortex0, texcoord + vec2(-texelSize.x,  texelSize.y)).rgb;
    vec3 col5 = texture(colortex0, texcoord + vec2( 0.0,			    texelSize.y)).rgb;
    vec3 col6 = texture(colortex0, texcoord + vec2( 0.0,			   -texelSize.y)).rgb;
    vec3 col7 = texture(colortex0, texcoord + vec2(-texelSize.x,  		    0.0)).rgb;
    vec3 col8 = texture(colortex0, texcoord + vec2( texelSize.x,  		    0.0)).rgb;

    vec3 colMax = max(currentFrame.rgb,max(col1,max(col2,max(col3, max(col4, max(col5, max(col6, max(col7, col8))))))));
    vec3 colMin = min(currentFrame.rgb,min(col1,min(col2,min(col3, min(col4, min(col5, min(col6, min(col7, col8))))))));
    
    vec3 clampedFrameHistory = clamp(frameHistory, colMin, colMax);

    float blendingFactor = mix(0.025, 1.0, smoothstep(0.05, 0.25, length(cameraPosition-previousCameraPosition)));
    blendingFactor = mix(1.0, blendingFactor, smoothstep(0.0, 0.1, roughness));
    reprojectFrame = mix(clampedFrameHistory, currentFrame.rgb, blendingFactor);
  }

  // return clamp(reprojectFrame,0.0,65000.0);
  return clamp(mix(reprojectFrame, upsampledCurrentFrame, outerEdgeResults),0.0,65000.0);
  // */

}

float blueNoise(){
	#ifdef TAA
  		return fract(texelFetch(noisetex, ivec2(gl_FragCoord.xy)%512, 0).a + 1.0/1.6180339887 * frameCounter);
	#else
		return fract(texelFetch(noisetex, ivec2(gl_FragCoord.xy)%512, 0).a + 1.0/1.6180339887);
	#endif
}

float interleaved_gradientNoise_temporal(){
	// #ifdef TAA
	// 	return fract(52.9829189*fract(0.06711056*gl_FragCoord.x + 0.00583715*gl_FragCoord.y ) + 1.0/1.6180339887 * frameCounter);
	// #else
	// 	return fract(52.9829189*fract(0.06711056*gl_FragCoord.x + 0.00583715*gl_FragCoord.y ) + 1.0/1.6180339887);
	// #endif

	vec2 coord = gl_FragCoord.xy;
	#ifdef TAA
		coord += (frameCounter%40000) * 2.0;
	#endif

	return fract(52.9829189*fract(0.06711056*coord.x + 0.00583715*coord.y ) + 1.0/1.6180339887);
}

vec2 decodeVec2(float a){
    const vec2 constant1 = 65535. / vec2( 256., 65536.);
    const float constant2 = 256. / 255.;
    return fract( a * constant1 ) * constant2 ;
}

vec3 decode (vec2 encn){
    vec3 n = vec3(0.0);
    encn = encn * 2.0 - 1.0;
    n.xy = abs(encn);
    n.z = 1.0 - n.x - n.y;
    n.xy = n.z <= 0.0 ? (1.0 - n.yx) * sign(encn) : encn;
    return clamp(normalize(n.xyz),-1.0,1.0);
}

vec3 toLinear(vec3 sRGB){
return sRGB * (sRGB * (sRGB * 0.305306011 + 0.682171111) + 0.012522878);
}

float shlickFresnelRoughness(float XdotN, float roughness){

	float shlickFresnel = clamp(1.0 + XdotN,0.0,1.0);

	float curves = exp(-4.0*pow(1.0-(roughness),2.5));
	float brightness = exp(-3.0*pow(1.0-sqrt(roughness),3.50));

	shlickFresnel = pow(1.0-pow(1.0-shlickFresnel, mix(1.0, 1.9, curves)),mix(5.0, 2.6, curves));
	shlickFresnel = mix(0.0, mix(1.0,0.065,  brightness) , clamp(shlickFresnel,0.0,1.0));
	
	return shlickFresnel;
}

void frisvad(in vec3 n, out vec3 f, out vec3 r){
    if(n.z < -0.9) {
        f = vec3(0.,-1,0);
        r = vec3(-1, 0, 0);
    } else {
    	float a = 1./(1.+n.z);
    	float b = -n.x*n.y*a;
    	f = vec3(1. - n.x*n.x*a, b, -n.x) ;
    	r = vec3(b, 1. - n.y*n.y*a , -n.y);
    }
}

mat3 CoordBase(vec3 n){
	vec3 x,y;
    frisvad(n,x,y);
    return mat3(x,y,n);
}

vec3 GGX(vec3 n, vec3 v, vec3 l, float r, vec3 f0, vec3 metalAlbedoTint) {
  r = max(pow(r,2.5), 0.0001);

  vec3 h = normalize(l + v);
  float hn = inversesqrt(dot(h, h));

  float dotLH = clamp(dot(h,l)*hn,0.,1.);
  float dotNH = clamp(dot(h,n)*hn,0.,1.) ;
  float dotNL = clamp(dot(n,l),0.,1.);
  float dotNHsq = dotNH*dotNH;

  float denom = dotNHsq * r - dotNHsq + 1.;
  float D = r / (3.141592653589793 * denom * denom);

  vec3 F = (f0 + (1. - f0) * exp2((-5.55473*dotLH-6.98316)*dotLH)) * metalAlbedoTint;
  float k2 = .25 * r;

  return dotNL * D * F / (dotLH*dotLH*(1.0-k2)+k2);
}

#ifdef IEXT_ENABLED
uniform bool IEXT_KEY_0;
#endif

vec4 calculateFlashlightData(in vec3 viewPos, bool hand){
    #ifdef IEXT_ENABLED
    if(IEXT_KEY_0) return vec4(0.0);
    #endif

  vec4 flashLightSpecularData = vec4(0.0);
  if(hand){
		return flashLightSpecularData;
	}

	// vec3 shiftedViewPos = viewPos + vec3(-0.25, 0.2, 0.0);
	// vec3 shiftedPlayerPos = mat3(gbufferModelViewInverse) * shiftedViewPos + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition) * 3.0;
	// shiftedViewPos = mat3(gbufferPreviousModelView) * shiftedPlayerPos + gbufferPreviousModelView[3].xyz;
	vec3 shiftedViewPos;
    vec3 shiftedPlayerPos;
	float forwardOffset;

    #ifdef VIVECRAFT
        if (vivecraftIsVR) {
	        forwardOffset = 0.0;
            shiftedPlayerPos = mat3(gbufferModelViewInverse) * viewPos + gbufferModelViewInverse[3].xyz + vivecraftRelativeMainHandPos;
            shiftedViewPos = shiftedPlayerPos * mat3(vivecraftRelativeMainHandRot);
        } else
    #endif
    {
	    forwardOffset = 0.5;
        shiftedViewPos = viewPos + vec3(-0.25, 0.2, 0.0);
        shiftedPlayerPos = mat3(gbufferModelViewInverse) * shiftedViewPos + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition) * 3.0;
        shiftedViewPos = mat3(gbufferPreviousModelView) * shiftedPlayerPos + gbufferPreviousModelView[3].xyz;
    }

    
    
  vec2 scaledViewPos = shiftedViewPos.xy / max(-shiftedViewPos.z - forwardOffset, 1e-7);
	float linearDistance = length(shiftedPlayerPos);
	float shiftedLinearDistance = length(scaledViewPos);

	float lightFalloff = 1.0 - clamp(1.0-linearDistance/FLASHLIGHT_RANGE, -0.999,1.0);
	lightFalloff = max(exp(-10.0 * FLASHLIGHT_BRIGHTNESS_FALLOFF_MULT * lightFalloff),0.0);

	float flashLightSpecular = lightFalloff * exp2(-7.0*shiftedLinearDistance*shiftedLinearDistance) * FLASHLIGHT_BRIGHTNESS_MULT;
	flashLightSpecularData = vec4(normalize(shiftedPlayerPos), flashLightSpecular);	

  return flashLightSpecularData;
}

#if defined IS_LPV_ENABLED || defined PHOTONICS && defined PHOTONICS && !defined PH_ENABLE_HANDHELD_LIGHT
  uniform bool firstPersonCamera;
	uniform vec3 relativeEyePosition;
  uniform int heldItemId;
  uniform int heldItemId2;

	uniform sampler3D texLpv1;
	uniform sampler3D texLpv2;

  #include "/lib/util.glsl"
	#include "/lib/hsv.glsl"
	#include "/lib/lpv_common.glsl"
	#include "/lib/lpv_render.glsl"
	#include "/lib/blocks.glsl"
	#include "/lib/lpv_blocks.glsl"

  vec3 GetHandLight(const in int itemId, const in vec3 playerPos, inout float lightRange) {
      vec3 lightFinal = vec3(0.0);

      uint blockData = imageLoad(imgBlockData, itemId).r;
      vec4 lightColorRange = unpackUnorm4x8(blockData);
      lightRange = lightColorRange.a * 255.0;

      if (lightRange > 0.0) {
          vec3 lightColor = srgbToLinear(lightColorRange.rgb);
          float lightDist = length(playerPos+relativeEyePosition)*0.7;
          float falloff = pow(1.0 - lightDist / lightRange, 3.0);
          lightFinal = lightColor * max(falloff, 0.0);
      }

      return lightFinal;
  }

  #ifdef BELTBORNE_LANTERNS
  uniform int IEXT_beltborne_lanterns_Id;
  #endif
#endif

float getReflectionVisibility(float f0, float roughness){

	// the goal is to determine if the reflection is even visible. 
	// if it reaches a point in smoothness or reflectance where it is not visible, allow it to interpolate to diffuse lighting.
	#if ROUGHNESS_THRESHOLD < 1
		return 0.0;
	#else
		float thresholdValue = ROUGHNESS_THRESHOLD/100.0;

		// the visibility gradient should only happen for dialectric materials. because metal is always shiny i guess or something
		float dialectrics = max(f0*255.0 - 26.0,0.0)/229.0;
		float value = 0.35; // so to a value you think is good enough.
		float thresholdA = min(max( (1.0-dialectrics) - value, 0.0)/value, 1.0);

		// use perceptual smoothness instead of linear roughness. it just works better i guess
		float smoothness = 1.0-sqrt(roughness);
		value = thresholdValue; // this one is typically want you want to scale.
		float thresholdB = min(max(smoothness - value, 0.0)/value, 1.0);
		
		// preserve super smooth reflections. if thresholdB's value is really high, then fully smooth, low f0 materials would be removed (like water).
		value = 0.1; // super low so only the smoothest of materials are includes.
		float thresholdC = 1.0-min(max(value - (1.0-smoothness), 0.0)/value, 1.0);
		
		float visibilityGradient = max(thresholdA*thresholdC - thresholdB,0.0);

		// a curve to make the gradient look smooth/nonlinear. just preference
		visibilityGradient = 1.0-visibilityGradient;
		visibilityGradient *=visibilityGradient;
		visibilityGradient = 1.0-visibilityGradient;
		visibilityGradient *=visibilityGradient;

		return visibilityGradient;
	#endif
}

/* RENDERTARGETS:12,3 */

void main() {

    vec2 texcoord = gl_FragCoord.xy*texelSize;
    float depth = texelFetch(depthtex1, ivec2(gl_FragCoord.xy),0).x;
    // bool hand = depth < 0.56;
    float z = depth;

    #if defined DISTANT_HORIZONS || defined VOXY
      float DH_depth1 = 1.0;
      float swappedDepth;
      if(z >= 1.0) {
        DH_depth1 = texelFetch(dhVoxyDepthTex1,ivec2(gl_FragCoord.xy),0).x;
        swappedDepth = DH_depth1;
      } else {
        swappedDepth = z;
      }
    #else
      float DH_depth1 = 1.0;
      float swappedDepth = z;
    #endif

    bool isSky = swappedDepth >= 1.0;
  
    gl_FragData[0] = vec4(0.0);
    vec3 FINAL_COLOR = texture(colortex3, texcoord).rgb;
    if(!isSky) {

      float frDepth = linearize(z);
      gl_FragData[0].a = frDepth;

      vec4 data = texelFetch(colortex1, ivec2(gl_FragCoord.xy), 0);

      vec4 dataUnpacked0 = vec4(decodeVec2(data.x),decodeVec2(data.y)); // albedo, masks
      vec4 dataUnpacked1 = vec4(decodeVec2(data.z),decodeVec2(data.w)); // normals, lightmaps

      float opaqueMasks = dataUnpacked1.w;
      bool hand = abs(opaqueMasks-0.75) < 0.01;
      bool entities = abs(opaqueMasks-0.45) < 0.01;
      bool opaqueParticles = abs(opaqueMasks-0.85) <0.01;
      #ifdef SHADER_GRASS
        bool isShaderGrass = abs(opaqueMasks-0.80) < 0.01;
      #else
        const bool isShaderGrass = false;
      #endif

      vec3 viewPos = toScreenSpace(vec3(texcoord/RENDER_SCALE, z));

      vec3 playerPos = mat3(gbufferModelViewInverse) * viewPos;
      vec3 NplayerPos = normalize(playerPos);
      playerPos += gbufferModelViewInverse[3].xyz;


      // derived from N and K from labPBR wiki https://shaderlabs.org/wiki/LabPBR_Material_Standard
      // using ((1.0 - N)^2 + K^2) / ((1.0 + N)^2 + K^2)
      const vec3 HCM_F0 [8] = vec3[8](
        vec3(0.531228825312, 0.51235724246, 0.495828545714),// iron	
        vec3(0.944229966045, 0.77610211732, 0.373402004593),// gold		
        vec3(0.912298031535, 0.91385063144, 0.919680580954),// Aluminum
        vec3(0.55559681715,  0.55453707574, 0.554779427513),// Chrome
        vec3(0.925952196272, 0.72090163805, 0.504154241735),// Copper
        vec3(0.632483812932, 0.62593707362, 0.641478899539),// Lead
        vec3(0.678849234658, 0.64240055565, 0.588409633571),// Platinum
        vec3(0.961999998804, 0.94946811207, 0.922115710997)	// Silver
      );

      vec3 albedo = toLinear(vec3(dataUnpacked0.xz,dataUnpacked1.x));
      vec2 lightmap = dataUnpacked1.yz;

      vec4 stuff = texelFetch(colortex8, ivec2(gl_FragCoord.xy), 0);
      vec3 normal = decode(stuff.xy);
      vec2 speculars = stuff.zw;

      float roughness = speculars.r;
      float f0 = speculars.g;
      roughness = 1.0 - roughness; 
      roughness *= roughness;

      f0 = f0 == 0.0 ? 0.02 : f0;

      bool isMetal = f0 > 229.5/255.0;

      // get reflected vector
      mat3 basis = CoordBase(normal);
      vec3 viewDir = -NplayerPos*basis;
      float VdotN = dot(-normalize(viewDir), vec3(0.0,0.0,1.0));
      float shlickFresnel = shlickFresnelRoughness(VdotN, roughness);

      // F0 <  230 dialectrics
      // F0 >= 230 hardcoded metal f0
      // F0 == 255 use albedo for f0
      albedo = f0 == 1.0 ? sqrt(albedo) : albedo;
      vec3 metalAlbedoTint = isMetal ? albedo : vec3(1.0);
      // get F0 values for hardcoded metals.
      vec3 hardCodedMetalsF0 = f0 == 1.0 ? albedo : HCM_F0[int(clamp(f0*255.0 - 229.5,0.0,7.0))];
      vec3 reflectance = isMetal ? hardCodedMetalsF0 : vec3(f0);
      vec3 F0 = (reflectance + (1.0-reflectance) * shlickFresnel) * metalAlbedoTint;

      vec4 currentFrame = texture(colortex0, texcoord);
      float alpha = getReflectionVisibility(f0, roughness);

      vec3 denoisedReflections = currentFrame.rgb;

      if(roughness > 0.005 && alpha < 0.9999 && !hand && !entities && !opaqueParticles
        #if defined DISTANT_HORIZONS || defined VOXY
          && z < 1.0
        #endif
      ) {
        denoisedReflections = VLTemporalFiltering2(texcoord, playerPos, frDepth, depthtex1, hand, currentFrame.rgb, roughness);

        gl_FragData[0].rgb = denoisedReflections;
      }

      denoisedReflections.rgb = mix(FINAL_COLOR, denoisedReflections.rgb, F0);
      FINAL_COLOR = mix(denoisedReflections.rgb, FINAL_COLOR, alpha);

      #if defined OVERWORLD_SHADER && SUN_SPECULAR_MULT > 0
        vec3 lightSourceReflection = SUN_SPECULAR_MULT * texture(colortex15, texcoord).rgb * GGX(normal, -NplayerPos, WsunVec, roughness, reflectance, metalAlbedoTint);
        FINAL_COLOR += lightSourceReflection;
      #endif

      #if defined FLASHLIGHT_SPECULAR && (defined DEFERRED_SPECULAR || defined FORWARD_SPECULAR)
        vec4 flashlightData = calculateFlashlightData(viewPos, hand);
        vec3 flashLightReflection = vec3(FLASHLIGHT_R,FLASHLIGHT_G,FLASHLIGHT_B) * flashlightData.a * GGX(normal, -flashlightData.xyz, -flashlightData.xyz, roughness, reflectance, metalAlbedoTint);
        FINAL_COLOR += flashLightReflection;
      #endif

      #if defined Hand_Held_lights && defined IS_LPV_ENABLED
        if(!hand && firstPersonCamera && !isShaderGrass) {
          if (heldItemId > 0){
            vec3 shiftedViewPos = viewPos + vec3(-0.25, 0.2, 0.0);
            vec3 shiftedPlayerPos = mat3(gbufferModelViewInverse) * shiftedViewPos + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition);

            float lightRange = 0.0;
            vec3 handLightCol = GetHandLight(heldItemId, shiftedPlayerPos, lightRange);

            if(lightRange > 0.0) {
              vec3 handheldReflection1 = handLightCol * GGX(normal, -shiftedPlayerPos, -shiftedPlayerPos, roughness, reflectance, metalAlbedoTint);
              FINAL_COLOR += handheldReflection1;
            }
          }

          if (heldItemId2 > 0){
            vec3 shiftedViewPos = viewPos + vec3(0.25, 0.2, 0.0);
            vec3 shiftedPlayerPos = mat3(gbufferModelViewInverse) * shiftedViewPos + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition);

            float lightRange = 0.0;
            vec3 handLightCol = GetHandLight(heldItemId2, shiftedPlayerPos, lightRange);

            if(lightRange > 0.0) {
              vec3 handheldReflection1 = handLightCol * GGX(normal, -shiftedPlayerPos, -shiftedPlayerPos, roughness, reflectance, metalAlbedoTint);
              FINAL_COLOR += handheldReflection1;
            }
          }

          #ifdef BELTBORNE_LANTERNS
            if (IEXT_beltborne_lanterns_Id > 0){
              vec3 shiftedViewPos = viewPos + vec3(0.15, 0.2, 0.0);
              vec3 shiftedPlayerPos = mat3(gbufferModelViewInverse) * shiftedViewPos + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition);

              float lightRange = 0.0;
              vec3 handLightCol = GetHandLight(IEXT_beltborne_lanterns_Id, shiftedPlayerPos, lightRange);

              if(lightRange > 0.0) {
                vec3 handheldReflection1 = handLightCol * GGX(normal, -shiftedPlayerPos, -shiftedPlayerPos, roughness, reflectance, metalAlbedoTint);
                FINAL_COLOR += handheldReflection1;
              }
            }
          #endif
        }
      #endif
      
    }

    gl_FragData[1].rgb = FINAL_COLOR;
}