

#define quarter_res_depth // turn off to use higher quality reflections. turning this on can cause artifacts.

//#define Specular_maps // reflections on blocks. REQUIRES A PBR RESOURCEPACK. this is also biome specific, some biomes are more wet than others, and you will see it with this on.
//#define Screen_Space_Reflections // toggle screenspace reflections. if you want normal performance but still want a bit of shiny, the sun reflection stays on when this is turned off.
#define Hardcoded_Metals // A color tweak controlled by the shader on certain metals like gold and stuff. will only change specific blocks, and things with the LabPBR predifined metal f0 values (f0 230 to 254). Do note, these colors arent physically accurate, i just like how they look.

#define Roughness_Threshold 1.5 // using a curve on the roughness, make the reflections more or less visible on rough surfaces [1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 ]
#define Sun_specular_Strength 3 // increase for more sparkles [1 2 3 4 5 6 7 8 9 10]

#define reflection_quality 30 // adjust the quality of the screenspace reflections. [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 ]
#define Roughness_Strength 1.0 // [0.0 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.85 0.9 0.95 1.0]
// #define Sky_reflection // just in case you dont want it i guess


// #define performance_mode // turns the roughness GGXVNDF off and hides reflections based on a roughness curve instead.
#define noise_mode 2 // 1 = least noise. 2 = medium noise. 3 = most noise. mode 3 has the most accurate roughness [1 2 3]
uniform sampler2D gaux1;
uniform int framemod8;

const vec2[8] offsets = vec2[8](vec2(1./8.,-3./8.),
									vec2(-1.,3.)/8.,
									vec2(5.0,1.)/8.,
									vec2(-3,-5.)/8.,
									vec2(-5.,5.)/8.,
									vec2(-7.,-1.)/8.,
									vec2(3,7.)/8.,
									vec2(7.,-7.)/8.);



float square(float x){
  return x*x;
}
float g(float NdotL, float roughness){
    float alpha = square(max(roughness, 0.02));
    return 2.0 * NdotL / (NdotL + sqrt(square(alpha) + (1.0 - square(alpha)) * square(NdotL)));
}
float ld(float dist) {
    return (2.0 * near) / (far + near - dist * (far - near));
}
float gSimple(float dp, float roughness){
  float k = roughness + 1;
  k *= k/8.0;
  return dp / (dp * (1.0-k) + k);
}

vec3 GGX2(vec3 n, vec3 v, vec3 l, float r, vec3 F0) {
  float alpha = square(r);

  vec3 h = normalize(l + v);

  float dotLH = clamp(dot(h,l),0.,1.);
  float dotNH = clamp(dot(h,n),0.,1.);
  float dotNL = clamp(dot(n,l),0.,1.);
  float dotNV = clamp(dot(n,v),0.,1.);
  float dotVH = clamp(dot(h,v),0.,1.);


  float D = alpha / (3.141592653589793*square(square(dotNH) * (alpha - 1.0) + 1.0));
  float G = gSimple(dotNV, r) * gSimple(dotNL, r);
  vec3 F = F0 + (1. - F0) * exp2((-5.55473*dotVH-6.98316)*dotVH);

  return dotNL * F * (G * D / (4 * dotNV * dotNL + 1e-7));
}
float invLinZ (float lindepth){
	return -((2.0*near/lindepth)-far-near)/(far-near);
}


vec3 toClipSpace3(vec3 viewSpacePosition) {
    return projMAD(gbufferProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}
float linZ(float depth) {
    return (2.0 * near) / (far + near - depth * (far - near));
	// l = (2*n)/(f+n-d(f-n))
	// f+n-d(f-n) = 2n/l
	// -d(f-n) = ((2n/l)-f-n)
	// d = -((2n/l)-f-n)/(f-n)

}


vec3 rayTraceSpeculars(vec3 dir,vec3 position,float dither, float quality, bool hand, float fres){

  vec3 clipPosition = toClipSpace3(position);
  float rayLength = ((position.z + dir.z * far*sqrt(3.)) > -near) ?
                     (-near -position.z) / dir.z : far*sqrt(3.);
  vec3 direction = normalize(toClipSpace3(position+dir*rayLength)-clipPosition);  //convert to clip space
  direction.xy = normalize(direction.xy);

  //get at which length the ray intersects with the edge of the screen
  vec3 maxLengths = (step(0.,direction)-clipPosition) / direction;
  float mult = min(min(maxLengths.x,maxLengths.y),maxLengths.z);

  vec3 stepv = direction * mult / quality*vec3(RENDER_SCALE,1.0);
  // if(hand) dither *= 0.1 ;
	vec3 spos = clipPosition*vec3(RENDER_SCALE,1.0) + stepv*dither;

	float minZ = spos.z+stepv.z;
	float maxZ = spos.z+stepv.z;

	spos.xy += TAA_Offset*texelSize*0.5/RENDER_SCALE;

  for (int i = 0; i <= int(quality); i++) {

		// decode depth buffer
    vec2 testthing = hand ? spos.xy*texelSize : spos.xy/texelSize/4.0; // fix for ssr on hand

		float sp = sqrt(texelFetch2D(gaux1,ivec2(testthing),0).w/65000.0);

		sp = invLinZ(sp);

    if(sp <= max(maxZ,minZ) && sp >= min(maxZ,minZ) ) return vec3(spos.xy/RENDER_SCALE,sp);
          
    spos += stepv;

	  //small bias
    float biasamount = 0.00015;
    if(hand) biasamount = 0.01;
	  // minZ = maxZ-clamp(fres*0.0004 ,0.00004,0.0004) / ld(spos.z);
	  minZ = maxZ-biasamount / ld(spos.z);

	  maxZ += stepv.z;
    
  }

  return vec3(1.1);
}

vec3 sampleGGXVNDF(vec3 V_, float alpha_x, float alpha_y, float U1, float U2, bool ishand){
	// stretch view
	vec3 V = normalize(vec3(alpha_x * V_.x, alpha_y * V_.y, V_.z));
	// orthonormal basis
	vec3 T1 = (V.z < 0.9999) ? normalize(cross(V, vec3(0,0,1))) : vec3(1,0,0);
	vec3 T2 = cross(T1, V);
	// sample point with polar coordinates (r, phi)
	float a = 1.0 / (1.0 + V.z);
  
  // float r = sqrt(U1);
 
  // float r = sqrt(mix(clamp(U1,alpha_x*0.9,alpha_x), mix(U2,U1,pow(alpha_x,0.1)), alpha_x));
	// U2 = mix( clamp(U2,0.0,0.75), U2, pow(alpha_y,0.75))  ;

#if noise_mode == 1
  float r = mix(sqrt(min(U1,0.1)),sqrt(U1),alpha_x) ;
  // U2 = mix(sqrt(max(U2,0.75)),U2,alpha_x) ;
#endif

#if noise_mode == 2
  float r = sqrt(mix(min(U1,0.2), U2, pow(alpha_x,0.75)));
#endif

#if noise_mode == 3
  float r = sqrt(U1);
#endif

  if(ishand){
    r = 0.0;
    U2 = 0.0;
  }

	float phi = (U2<a) ? U2/a * 3.141592653589793 : 3.141592653589793 + (U2-a)/(1.0-a) * 3.141592653589793;
	float P1 = r*cos(phi);
	float P2 = r*sin(phi)*((U2<a) ? 1.0 : V.z);
	// compute normal
	vec3 N = P1*T1 + P2*T2 + sqrt(max(0.0, 1.0 - P1*P1 - P2*P2))*V;
	// unstretch
	N = normalize(vec3(alpha_x*N.x, alpha_y*N.y, max(0.0, N.z)));
	return N;
}

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);
}
float unpackRoughness(float x){
  float r = 1.0 - x;
  return clamp(r*r,0,1);
}
