// This shader is very similar to the simple Lambertian shader with a shadow
//    map query.  I have removed most of those comments to reduce the size of
//    this shader code.

#version 400

#pragma IGLU_ENABLE  DEPTH_TEST
#pragma IGLU_DISABLE BLENDING
#pragma IGLU_DISABLE COLOR_LOGIC_OP

// Data needed from vertex shader
in vec4 esFragPos;
in vec4 esFragNormal;
in float fragMatlID;

// Where to store the color we compute
out vec4 result;

// Uniforms needed for basic Lambertian shading w/parabolic shadow maps
uniform float sMapBias, nearDist, farDist, clampToVisible, useVSVForSurface, vsvBias;
uniform vec4 esLightPos;     
uniform sampler2DArrayShadow shadowTex; 
uniform samplerBuffer  matlInfoTex; 
uniform vec2 padScaleCoef;

// Data needed for visualizing voxelized shadow volumes
uniform usampler2D voxels0, voxels1, voxels2, voxels3;
uniform float intensity;
uniform float maxAngle_L_V, minAngle_L_V;

// Takes a few important vectors (eye-to-light, eye-to-frag) and computes epipolar coords
vec2 ComputeEpipolarVoxelCoordinates( vec3 EtoL, vec3 EtoF )
{
	// Compute the x-coordinate in the epipoloar map
	float eyeAngle = clamp( dot(EtoF,EtoL), -1.0f, 1.0f );
	float epiNDC_xCoord = (acos( eyeAngle ) - minAngle_L_V) / (maxAngle_L_V - minAngle_L_V);

	// Find a ortho normal basis, used to compute the epipolar angle
	vec3 up      = normalize( cross( EtoL, vec3(0,0,-1) ) );
	vec3 forward = cross( up, EtoL );
	vec2 angle = vec2( dot( forward, EtoF ), dot( up, EtoF ) );
	float epiPlaneAngle = atan( angle.y, angle.x );

	// Compute the y-coordinate in the epipolar map
	float epiNDC_yCoord = 0.5 * clamp( epiPlaneAngle/3.14159265358979, -1.0, 1.0 ) + 0.5;
	
	// What's the pad scale?  Well, if our VSV is not a power of 2, we're padding up to
	//    the next size, so we have to scale back down to ensure we grab the right voxel
	return vec2( 1.0-epiNDC_xCoord, epiNDC_yCoord )*padScaleCoef;
}

// A function to query our paraboloid shadow map
float IsIlluminatedByOmniLight( vec3 lightPos, vec3 fragPos )
{
	vec3 toVert = fragPos - lightPos;
	float dist = length( toVert );
	toVert = normalize( toVert );
	
	// The [0..1] z-value to compare against the shadow map
	float z_persp = 0.5 * ((nearDist+farDist)*-dist+(2*nearDist*farDist))/(dist*(nearDist-farDist)) + 0.5;
	
	// Compute the coordinates in the + & - hemi-paraboloids
	vec2 posMap = vec2(toVert.xy)/(toVert.z+1);
	vec2 negMap = vec2(toVert.xy)/(toVert.z-1);
	
	// Select if we're using the + or - hemi-paraboloid.
	bool usePos = dot(posMap,posMap) < dot(negMap,negMap);
	
	// Now map this into texture space (i.e., [0..1])
	posMap = 0.5*posMap + 0.5;
	negMap = 0.5*negMap + 0.5;
	
	// Lookup the value
	return texture( shadowTex, vec4( usePos ? vec3(posMap, 0) : vec3(negMap, 1), z_persp+sMapBias ));
}

// Set a singular bit in a uvec4
uvec4 SetFragmentBit( int layer, int iFragPos ) 
{
	int whichBit = iFragPos - int(layer*128);
	uvec4 shiftBits = uvec4( whichBit ) - uvec4( 0u, 32u, 64u, 96u );
	uvec4 bitToShift = uvec4(1u) - min( uvec4(1u), shiftBits & uvec4(0xFFFFFFE0u) );
	return ((whichBit >= 0) && (whichBit < 128)) ? bitToShift << shiftBits : uvec4(0u);
}

// Set all the bits in a uvec4 less than a specified bit.  (Could probably be better coded.)
uvec4 SetBitsLessOrEqual( uint whichBit )
{
	uvec4 shiftBits = uvec4( max( ivec4( 0 ), ivec4( 32, 64, 96, 128 ) - ivec4( whichBit ) ) );
	uvec4 bitsToShift = uvec4(0xFFFFFFFFu)*(uvec4(1u) - min( uvec4(1u), shiftBits & uvec4(0xFFFFFFE0u) ));
	return bitsToShift >> min( uvec4(31u), shiftBits );
}

// We only want the voxel bits in front of our visible fragment.  This function takes the 
//    MRT number (layer number) and the integer voxel location of the fragment, and sets 1s
//    in all voxels in that MRT that should be used in computation.
uvec4 SetValidBitsInLayer( int layer, int iFragPos )
{
	int deltaFactor = iFragPos - int(layer*128);
    uvec4 tmp = ( deltaFactor <= 0 && clampToVisible > 0 ) ? uvec4(0x0u) : uvec4(0xFFFFFFFFu);
	return ( (deltaFactor > 0) && (deltaFactor < 128) && (clampToVisible > 0) ) ? 
	               SetBitsLessOrEqual( uint(deltaFactor) ) : // Inside range 1..127 (need special bit pattern)
	               tmp;                                      // Can use all 0s or all 1s for our bit pattern
}

// This is the actual code that is executed.
void main( void )
{	
	// Look up data about this particular fragment's material
	int matlIdx     = 4 * int(fragMatlID+0.5);
	vec4 matlInfo   = texelFetch( matlInfoTex, matlIdx+1 );  // Get diffuse color
	float isLit     = IsIlluminatedByOmniLight( esLightPos.xyz, esFragPos.xyz );
	
	// Find simple vectors for basic shading
	vec3 toLight  = normalize( esLightPos.xyz - esFragPos.xyz);
	vec3 toEye    = normalize( -esFragPos.xyz );
	
	// Find our entry in the voxelized shadow volume
	vec2 epiCoord = ComputeEpipolarVoxelCoordinates( normalize(esLightPos.xyz), -toEye );
	
	// Find the current fragment's phi-location (z-value if using texture xyz) in the voxel grid
	//    -> Computed as light angle [0..pi] between eye & frag, discretized into 512 blocks (4 MRTs)
	//    -> Second version is biased, to avoid numerical errors for surface shadows
	int iFragPos  = int( acos(dot(toLight,normalize(esLightPos.xyz))) * 512.0 / 3.14159265358979 );
	int iFragBias = int( (512.0*acos(dot(toLight,normalize(esLightPos.xyz)))/3.14159265358979)-vsvBias );
	
	// Read in the voxels for this eye ray from our voxelized shadow map
	uvec4 texels[4];
	texels[0] = texture( voxels0, epiCoord );
	texels[1] = texture( voxels1, epiCoord );
	texels[2] = texture( voxels2, epiCoord );
	texels[3] = texture( voxels3, epiCoord );
	
	// Compute intensity for VSV visualization
	uvec4 bitsInShadow = uvec4( 0u ), dotVec = uvec4( 1u );
	bitsInShadow += bitCount( texels[0] & SetValidBitsInLayer( 0, iFragPos ) );
	bitsInShadow += bitCount( texels[1] & SetValidBitsInLayer( 1, iFragPos ) );
	bitsInShadow += bitCount( texels[2] & SetValidBitsInLayer( 2, iFragPos ) );
	bitsInShadow += bitCount( texels[3] & SetValidBitsInLayer( 3, iFragPos ) );
	float heat = intensity * float( dot( bitsInShadow, dotVec ) ) / 512.0; 
	
	// Identify if this fragment is (itself) shadowed by the VSV (allows for surface shadows)
	uvec4 isSurfaceShadow = uvec4( 0u );
	isSurfaceShadow += bitCount( texels[0] & SetFragmentBit( 0, iFragBias ) );
	isSurfaceShadow += bitCount( texels[1] & SetFragmentBit( 1, iFragBias ) );
	isSurfaceShadow += bitCount( texels[2] & SetFragmentBit( 2, iFragBias ) );
	isSurfaceShadow += bitCount( texels[3] & SetFragmentBit( 3, iFragBias ) );
	float vsvLit = dot( isSurfaceShadow, dotVec ) > 0 ? 0.0 : 1.0;
	isLit = (useVSVForSurface > 0) ? vsvLit : isLit;
	
	// Figure out the Lambertian color (if shadowed, set to 0)
	vec3 lambert  = isLit * max( 0.0f, dot( normalize(esFragNormal.xyz), toLight ) ) * matlInfo.xyz;
	
	// The ambient & lambertian terms we computed above are mutually
	//    independent, so we simply add them together, giving us a correct
	//    answer, no matter which display approach the user has chosen.
	result = vec4( (1.0-heat)*lambert, 1.0f ) + vec4( heat, heat, 0, 0 );
}