uniform sampler2D 	colorTex,
					normalTex,
					matTex,
					posTex;
float				Pi=3.14159265359;
uniform float		maxdist;
uniform float 		srcRadius;
uniform float 		srcLen;

#ifdef SPOT

	uniform vec3 		lightdir;
	uniform float 		lightCos;

	#ifdef PROJECTION

		uniform sampler2D 	lightTex;

	#endif

#endif

#ifdef OMNI

uniform vec2		prj;

	#ifdef PROJECTION

		uniform samplerCube 	projTex;

	#endif

#endif


#ifdef SHADOW

uniform sampler2DShadow	shadowmap;

	#ifdef OMNI

	uniform samplerCube	indCube;

	#endif

#endif

uniform vec3 specularPalette[8];
uniform vec3 scatterPalette[8];

varying vec2 		texcoord;
uniform vec3 		lightpos;
uniform vec3		lightcolor;
uniform vec2		lightscale;
uniform float		lightrange,
					lightfallof,
					lightfallofExp,
					fallofBias,
					cOfs;
uniform vec3 		lightRdir;

uniform vec4		LTM0,
					LTM1,
					LTM2,
					LTM3;

varying vec2		VPOS;
uniform float 		bias;

//const float			FALLOF_BIAS=0.1;

//vec4 scatterColor = vec4(0.15, 0.0, 0.0, 1.0);

vec4 decode(vec4 enc)
{
    vec2 fenc = enc.xy*4.0-2.0;
    float f = dot(fenc,fenc);
    float g = sqrt(1.0-f/4.0);
    vec4 n;
    n.xy = fenc.xy*g;
    n.z = 1.0-f/2.0;
	n.w=enc.w;
    return n;
}

// --------------------AREA LIGHTS---------------------------------------------

float gSphereSinAlpha=0.0;
float gLineCosSubtended=0.0;

vec3 LineIrradiance( vec3 N, vec3 L0, vec3 L1, float DistanceBiasSqr, out float CosSubtended, out float BaseIrradiance, out float NdotL )
{
	float dotL0 = dot( L0, L0 );
	float dotL1 = dot( L1, L1 );
	float InvLen0 = 1.0/sqrt( dotL0 );
	float InvLen1 = 1.0/sqrt( dotL1 );
	float InvLen01 = InvLen0 * InvLen1;

	CosSubtended = dot( L0, L1 ) * InvLen01;
	BaseIrradiance = InvLen01 / ( CosSubtended * 0.5 + 0.5 + DistanceBiasSqr * InvLen01 );
	NdotL = 0.5 * ( dot(N, L0) * InvLen0 + dot(N, L1) * InvLen1 );
	
	return ( BaseIrradiance * 0.5 ) * ( L0 * InvLen0 + L1 * InvLen1 );
}

vec3 SphereLightVector(vec3 R, vec3 L, float lightRadius, vec3 P)
{
	vec3 centerToRay=dot(L,R)*R - L;
	vec3 CP = L + centerToRay * clamp(lightRadius/length(centerToRay),0.0,1.0);
	return CP;
}

vec3 LineLightVector(vec3 R, vec3 L0, vec3 L1, vec3 pos, float lightlen)
{
	vec3 Ld = L1 - L0;
	float len2=lightlen*lightlen;
	float dotLR=dot(R,Ld);
	
	float t= dot(L0,R*dotLR - Ld) / (len2 - dotLR*dotLR);
//	float t=(dot(L0,R)*dotLR - dot(L0,Ld)) / (len2 - (dotLR*dotLR));
	vec3 CP = L0 + clamp(t,0.0,1.0)*Ld;
//	vec3 centerToRay=dot(CP,R)*R - CP;
//	CP = CP + centerToRay * clamp(lightRadius/length(centerToRay),0.0,1.0);
	return CP;
}

vec3 ClosestPointOnLine(vec3 L0, vec3 L1, float len)
{
	vec3 Ld = L1 - L0;
	float t=dot(-L0,Ld)/(len*len);
	return L0 + clamp(t,0.0,1.0)*Ld;	
}

// Alpha is half of angle of spherical cap
float SphereHorizonCosWrap( float NdotL, float SinAlphaSqr )
{
	float SinAlpha = sqrt( SinAlphaSqr );
	if( NdotL < SinAlpha )
	{
		NdotL = max( NdotL, - SinAlpha );
		NdotL = pow( SinAlpha + NdotL, 2.0 ) / ( 4.0 * SinAlpha );
	}
	return NdotL;
}

float ComputeA2( float a2, float SinAlpha, float VdH )
{
	return a2 + 0.25 * SinAlpha * (3.0 * sqrt(a2) + SinAlpha) / ( VdH + 0.001 );
}

float EnergyNormalization( inout float a2, float VoH )
{
	float SphereA2 = a2;
	float Energy = 1.0;
	if( gSphereSinAlpha > 0.0 )
	{
		SphereA2 = ComputeA2( a2, gSphereSinAlpha, VoH );
		Energy = a2 / SphereA2;
	}

	if( gLineCosSubtended < 1.0 )
	{
		float LineCosTwoAlpha = gLineCosSubtended;
		float LineTanAlpha = sqrt( ( 1.0001 - LineCosTwoAlpha ) / ( 1 + LineCosTwoAlpha ) );
		float Line_a2 = ComputeA2( SphereA2, LineTanAlpha, VoH );
		Energy *= sqrt( SphereA2 / Line_a2 );
	}

	return Energy;
}

vec3 getSpecularDominantDirArea(vec3 N, vec3 R, float roughness)
{
	// Simple linear approximation 
	float lerpFactor = (1 - roughness);

	return normalize(mix(N, R, lerpFactor));
}

// ------------------ PBR ---------------------------------
float phong_diffuse()
{
	return (1.0 / Pi);
}

vec3 fresnel_factor(vec3 f0, float f90, float product)
{
	return f0 + (vec3(f90) - f0) * pow(1.0 - product, 5.0);
	//return mix(f0, vec3(1.0), pow(1.01 - product, 5.0));
}

float D_GGX(float a2, float NdH)
{
	//float m = roughness * roughness;
	float d = (NdH * a2 - NdH) * NdH + 1.0;
	return a2 / (Pi*d*d);
}

float G_schlick(float roughness, float NdV, float NdL)
{
	float r=(roughness+1.0)/2.0;
	float k=r*r*0.5;//0.125;
	float V = NdV * (1.0 - k) + k;
	float L = NdL * (1.0 - k) + k;
	return /*1.0/*/0.25/(V*L);
}

float G_Smith( float a2, float NdV, float NdL )
{
	float V = NdV + sqrt( NdV * (NdV - NdV * a2) + a2 );
	float L = NdL + sqrt( NdL * (NdL - NdL * a2) + a2 );
	return 1.0/( V*L );
}

float G_SmithJointApprox( float a, float NdV, float NdL )
{
	float V = NdL * ( NdV * ( 1 - a ) + a );
	float L = NdV * ( NdL * ( 1 - a ) + a );
	return 0.5 /( V+L );
}

vec3 CookTorrance(float EdotH, float NdL, float NdV, float NdH, vec3 F, float rough, float a)
{
	float a2=a*a;
	float energy=EnergyNormalization(a2,EdotH);
	float D = D_GGX(a2, NdH)*energy;
//	float G = G_schlick(rough, NdV, NdL);
	float G = G_SmithJointApprox(a,NdV, NdL);//G_schlick(rough, NdV, NdL);
	//float G=G_Smith( a2, NdV, NdL );
	return max(D*G,0.0)*F;// (D*F*G) / mix(1.0-(roughness*0.9),1.0,4*NdL*NdV); 
} 

// --------------------------------------------------------------------------

void main()
{
	vec4	XYproj;	
	vec4 	projcoords;
	vec3 	projcolor;

	vec4 color = texture2D(colorTex,texcoord.st);
	vec4 encoded=texture2D(normalTex,texcoord.st);
	vec4 normal = decode(encoded);
	float reflPow=encoded.z;
	
	float thickness=normal.a;
	float scatter=color.a;
	float shadowOcc=1.0;
	
	vec4 pos;
	pos.z = texture2D(posTex,texcoord.st).r;
	vec4 mat = texture2D(matTex,texcoord.st);
	float metal=mat.x*step(scatter,0.0);

	// get colors
	int i1=int(mat.x*256.0);
	int i2=i1;
	i1=i1/8;	// specular index
	i2=i2-(i1*8);
	vec3 scatterColor=pow(scatterPalette[i2],vec3(2.2));
		
	// compute position
	pos.xy=VPOS.xy*-pos.z;
	pos.w=1.0;
	
	float mindist=maxdist-1000.0;
	float t=1.0-( clamp( (length(pos)-mindist)/(maxdist-mindist),0.0,1.0) );
	if(t<=0.0)
		shadowOcc=0.0;

	// COMPUTE LIGHT TEXTURE PROJECTION + ATTENUATION
	XYproj.x = dot(pos,LTM0);
	XYproj.y = dot(pos,LTM1);
	XYproj.z = dot(pos,LTM2);
	XYproj.w = dot(pos,LTM3);
	
	vec3 uldir=lightpos-pos.xyz;
	vec3 ldir=normalize(uldir);
	///////////////////////////////////


#ifdef SHADOW

	
	#ifdef SPOT
		projcoords=XYproj;
		projcoords.z=projcoords.z-tan(acos(abs(dot(ldir,normal.xyz))))*bias;/*bias;*///tan(acos(clamp(dot(ldir,normal.xyz),0.0,1.0)))*bias;/*bias;*/
		projcoords.xyz=projcoords.xyz/projcoords.w;
	#endif
	
	#ifdef OMNI

		float MA;
	
		MA=max(abs(XYproj.x),abs(XYproj.y));
		MA=max(MA,abs(XYproj.z));

		projcoords.xy=textureCube(indCube, XYproj.xyz).xy;
		projcoords.z=-(prj.y/-MA)-prj.x;
		projcoords.z=projcoords.z*0.5+0.5;
		projcoords.z=projcoords.z-tan(acos(abs(dot(ldir,normal.xyz))))*bias;/*bias;*///tan(acos(clamp(dot(ldir,normal.xyz),0.0,1.0)))*bias;/*bias;*/
		
	#endif

	float dOfsX = cOfs*0.5;
	float dOfsY = dOfsX;
	
	#ifdef OMNI
		dOfsX*=0.5;
	#endif

	shadowOcc=0.0;

	vec3 shadowcoords;
	shadowcoords.z=projcoords.z;

	shadowcoords.xy=projcoords.xy+vec2(-dOfsX,dOfsY);
	shadowOcc+=shadow2D(shadowmap,shadowcoords).r;

	shadowcoords.xy=projcoords.xy+vec2(dOfsX,dOfsY);
	shadowOcc+=shadow2D(shadowmap,shadowcoords).r;

	shadowcoords.xy=projcoords.xy+vec2(dOfsX,-dOfsY);
	shadowOcc+=shadow2D(shadowmap,shadowcoords).r;

	shadowcoords.xy=projcoords.xy+vec2(-dOfsX,-dOfsY);
	shadowOcc+=shadow2D(shadowmap,shadowcoords).r;

	shadowOcc *= 0.25;

#endif
	
	vec4 finalcolor=color;
	gl_FragColor=vec4(0,0,0,0);

	if(shadowOcc>0.0)
	{
		float lightDistance=length(uldir);
		float invlightDist=1.0/lightDistance;
		float vDist=lightDistance/lightrange;
	
		float LineCosSubtended = 1;
		float fallof=0;
		float rough=clamp(sqrt(mat.y),0.05,1.0);	// clamp for preventing division by 0
		float alpha = rough*rough;
		
		vec3 E=-pos.xyz;
		E=normalize(E);
		
		// DIFFUSE /////////////////////////////////////////////////////////////////
		float NdotL = 0;//clamp(dot(ldir,normal.xyz),0.0,1.0);	
		vec3 P0;
		vec3 P1;
		vec3 L0=uldir;
		vec3 L1=uldir;	
		if(srcLen>0.0)
		{
			P0 = lightpos - lightRdir*srcLen*0.5;
			P1 = lightpos + lightRdir*srcLen*0.5;
			L0 = P0 - pos.xyz;
			L1 = P1 - pos.xyz;		
			LineIrradiance( normal.xyz, L0, L1, fallofBias, LineCosSubtended, fallof, NdotL );
		}
		else
		{
			fallof=1.0/((lightDistance*lightDistance) + fallofBias);
			vec3 L=uldir*invlightDist;
			NdotL=dot(L,normal.xyz);
		}
		if(srcRadius>0.0)
		{
			float srcRadius2=srcRadius*srcRadius;
			float sinAlpha=clamp(srcRadius2*fallof,0.0,1.0);
			NdotL=SphereHorizonCosWrap(NdotL,sinAlpha);
		}	
		 
		 // saturate NdotL
		 NdotL = clamp(NdotL,0.0,1.0);	
		 gSphereSinAlpha = clamp(srcRadius*invlightDist*(1.0-alpha),0.0,1.0);
		 gLineCosSubtended = LineCosSubtended;
		 
		// scatter
		float scatterNdotL=(NdotL+scatter)/(1+scatter);
		scatterNdotL=max(scatterNdotL,0.0);
		float finalscatter = smoothstep(0.0,0.3,scatterNdotL)*smoothstep(0.6,0.3,scatterNdotL);
		float finalNdotL=mix(scatterNdotL,NdotL,metal);
		vec3 finalScatterColor=mix(vec3(scatterColor*finalscatter*scatter*color.xyz),vec3(0.0),metal);
		
		// backscatter
		vec3 backScatter=vec3(0.0,0.0,0.0);
		if(thickness>0.0)
		{
			vec3 LTLight=ldir-normal.xyz;
			float LTdot=pow(clamp(dot(E,-LTLight),0.0,1.0),2.0)*scatter*thickness*0.33;
			backScatter = mix(vec3(LTdot)*color.xyz*scatterColor,vec3(0.0),metal);
		}
		
		// DECAY //////////////////////////////////////////////
		float decay=pow(clamp(1.0 - pow(vDist,lightfallofExp),0.0,1.0),2.0)*fallof;
	
		#ifdef SPOT

			float maxConeAng=(1.0-lightCos);
			float minConeAng=(1.0-lightfallof);
			float angRange=maxConeAng-minConeAng;
			float cos=1.0-dot(lightdir,ldir);
			cos=max(cos-minConeAng,0.0);
			
			float spotFallof=clamp(1.0 - (cos/angRange),0.0,1.0);
			float att=decay*spotFallof;
			projcolor= vec3(att);			
			
			#ifdef PROJECTION
			
				float lightlod=pow(vDist,/*0.5*/1.0/lightfallofExp)*8.0;
				projcolor=texture2DLod(lightTex,XYproj.xy/XYproj.w,lightlod).xyz*projcolor;
					
			#endif

		#endif

		#ifdef OMNI

			
			projcolor= vec3(decay);
			
			#ifdef PROJECTION
			
				float lightlod=pow(vDist,0.5)*8.0;
				projcolor=textureCubeLod(projTex,XYproj.xyz,lightlod).xyz*projcolor;
				
			#endif

		#endif
		
		// SPECULAR /////////////////////////////////////////////////////////////
		vec3 REFL=mix(vec3(0.08)*reflPow*reflPow,color.xyz/*reflPow*/,metal);//max(ffresnel.x,0.0);
	
		// compute all vectors
		vec3 R=reflect(E,normal.xyz);
		//R=getSpecularDominantDirArea(normal.xyz, R, rough);
		
		if(srcLen>0.0)		
		{	
			/*vec3 CP = ClosestPointOnLine( L0, L1, srcLen );
			vec3 CPdir = normalize( CP );
			R = mix( R, CPdir, clamp( dot( CPdir, R ) , 0.0, 1.0) );
			R = normalize( R );
		*/
			uldir=LineLightVector(R, L0, L1, pos.xyz, srcLen);
		}
		
		//vec3 L=uldir/lightDistance;
		//alphaPrime = clamp( alpha + (srcRadius / (length(uldir)*2.0)) ,0.0,1.0);
		if(srcRadius>0.0)
			uldir=SphereLightVector(R, uldir, srcRadius, pos.xyz);
		
		ldir=normalize(uldir);
		//NdotL = clamp(dot(ldir,normal.xyz),0.0,1.0);	
		
		vec3 H = normalize(ldir+E);
		float NdotH = clamp(dot(H,normal.xyz),0.0,1.0);
		float EdotH = clamp(dot(E,H),0.0,1.0);
		float NdotE = clamp(abs(dot(normal.xyz,E)),0.0,1.0);
		
		// specular
		vec3 specfresnel=vec3(0.0,0.0,0.0);
		vec3 specular=vec3(0.0,0.0,0.0);		
		
		if(lightscale.y>0.0)
		{
			float rmax = max(max(REFL.r, REFL.g), REFL.b);
			float REFL90 = clamp(rmax*25.0,0.0,1.0);
			specfresnel=fresnel_factor(REFL, REFL90, EdotH);
			specular = CookTorrance(EdotH, NdotL, NdotE, NdotH, specfresnel, rough, alpha) * NdotL * lightscale.y;
		}
		
		vec3 diffuse = ((vec3(1.0) - specfresnel) * phong_diffuse() * finalNdotL) + finalScatterColor;
		
		// final color
		finalcolor = mix(color,vec4(0.0),metal)*vec4(diffuse,1.0) + vec4(specular,1.0) + vec4(backScatter,1.0);
		gl_FragColor.xyz=max((finalcolor.xyz*lightcolor.xyz)*lightscale.x*projcolor*shadowOcc*t,0.0);
	}
}
