//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This work is licensed under a Creative Commons Attribution 3.0 Unported License.												//
// You are free to share, modify and adapt it for your needs, and even use it for commercial use.								//
// I would also love to hear about a project you are using it with.																//
// https://creativecommons.org/licenses/by/3.0/us/																				//
//																																//
// This shader recalulates the dynamic clipping planes, linearizes depth and automaically adjusts convergence to maximize depth	//
//																																//
// Cheers!																														//
// Stephen Shepard aka sgsrules 																								//
//																																//
// Contact: sgsrules3dfixes@gmail.com																							//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#version 450
#define customConvergence
in vec2 vertex;
out vec2 texCoord;
out vec4 clipcoord;
out vec4 minmax;
uniform sampler2D dataTex;
uniform sampler2D depthTex;

#define startNear .02
#ifdef customConvergence
uniform float convergence;
#else
float convergence = .55;
#endif

float zpd = 12;
float far = 1;
float maxConvergence =.5;
float maxNearClip = .1;
float linAvgThreshold =.2;
float avgThreshold = .03;
//-----------------------------------------------------------------------------

float LinearizeDepth(float depth, float cN, float cF)
{
	return 2.0 * cN * cF / (cF + cN - (2.0 * depth - 1.0) * (cF - cN));
}
//-----------------------------------------------------------------------------

float GetNearFromDepth(float targetLinearDepth, float depth)
{
 float cN = ((depth-1)*targetLinearDepth)/(depth*targetLinearDepth-1);
 return cN;
}
//-----------------------------------------------------------------------------

void main()
{
	gl_Position    = vec4(vertex,0,1.0);
	texCoord = vertex*.5+.5;

//-----------------------------------------------------------------------------
// Below is an example of Clipping planes calculations used in Zelda Breath of the Wild
// Modify it to your liking!
// It still works fine for most games (without dynamic clipping planes).
//-----------------------------------------------------------------------------

	const int checkSampleCount = 106;
	vec2 checkSampleCoord[checkSampleCount];
	
	for (int x= 0;x<20;x++)
	{
		for (int y= 0;y<5;y++)
		{
			checkSampleCoord[x+y*20]=vec2(x*.05+.05,y*.1+.075);
		}
	}
	
	// extra samples for glider
	checkSampleCoord[100] = vec2(.5,.6);
	checkSampleCoord[101] = vec2(.53,.6);
	checkSampleCoord[102] = vec2(.47,.6);
	checkSampleCoord[103] = vec2(.5,.55);
	checkSampleCoord[104] = vec2(.53,.55);
	checkSampleCoord[105] = vec2(.47,.55);
	 
	float checkSample[checkSampleCount];
	float csmin=1;
	float csminw =1;
	float csmax=0;
	float avg=0;
	float tw =0;
	vec2 c = vec2(.5,.4);
	for(int i=0;i<checkSampleCount;i++)
	{
		checkSample[i] = texture2D(depthTex,vec2(checkSampleCoord[i].x,1-checkSampleCoord[i].y)).x;
	
		csmin = min(checkSample[i],csmin);
		csmax = max(checkSample[i],csmax);
		if(checkSample[i]<.1||checkSample[i]>.999)continue;
	
		float weight = length(checkSampleCoord[i]-c);
		//	float cw = max(pow(weight,6)-.1,0);
		float cw = max(pow(weight,1.5)-.2,0.);
		csminw = min(mix(checkSample[i],1.,cw),csminw);
		tw+=weight;
		avg+=checkSample[i]*weight;
	}
	minmax.x = csminw;
	
	avg/=tw;
	clipcoord.z = 1;
	bool isFlat = true;
	
	vec4 pclip =texture2D(dataTex,vec2(.25,.5));
	vec4 pclip2 =texture2D(dataTex,vec2(.75,.5));
	
	for(int i=0;i<checkSampleCount-1;i++)
	{
		if(checkSample[i]!=checkSample[i+1])
		{
			isFlat= false;
			break;
		}
	}
	if(isFlat)
	{
		clipcoord.z = -1;
		clipcoord.x =0;
		minmax.z = zpd;
		return;
	}

	minmax.y = LinearizeDepth(avg,pclip.x,far);
	float pavg = pclip.w;
	clipcoord.w = avg;
	clipcoord.y = 1;
	if(pclip.x>.99999||pclip.x<.00001||pclip.z<0)
	{
		clipcoord.x=startNear;
		minmax.z = zpd;
		return;
	}
	else
	{
		clipcoord.x= pclip.x;
		minmax.z = pclip2.z;
	}
	 if((abs(pavg-avg)>avgThreshold)||(abs(pclip2.y-minmax.y)>linAvgThreshold))
	{
		clipcoord.y = 0;
		float nn = LinearizeDepth(pavg,pclip.x,far);
		clipcoord.x= GetNearFromDepth(nn,avg);
	}

	clipcoord.x=min(clipcoord.x,maxNearClip);
	float lmin =LinearizeDepth(csminw,clipcoord.x,far);

	if(avg<.95)
	{
		if(clipcoord.x<startNear)clipcoord.x=mix(clipcoord.x,startNear,.1);
		float lmin2 =GetNearFromDepth(lmin,csminw);
		if(lmin>.04)clipcoord.x=mix(clipcoord.x,lmin2,.1);
	}
	minmax.x = LinearizeDepth(csminw,clipcoord.x,far);
	float zspeed =clamp( pow(1 - min(minmax.z,minmax.x*convergence) / max(minmax.z,minmax.x*convergence),3.) , 0.0, 1.0);
	minmax.z = min(mix(minmax.z,minmax.x*convergence,.3*zspeed),maxConvergence);
}
//-----------------------------------------------------------------------------
