/*************************************************************************
** Mode_ParaboloidShadowMaps.cpp                                        **
** --------------------------                                           **
**                                                                      **
** This display mode renders a cow inside a Cornell Box with shadows    **
**    from a paraboloid shadow map.  When this display is active, a     **
**    variety of variables are modifiable from the widget window to     **
**    change the behavior of the display.                               **
**                                                                      **
** Chris Wyman (04/06/2012)                                             **
*************************************************************************/

#include "../projectIncludes.h"
#include "Mode_SampleVoxelsFromShadowMap.h"


// A global data structure containing all shared data for this program
extern RenderingData *data;


void ReturnMinAndMaxLightAngles( const vec4 &eslightPos, float *minDistance, float *maxDistance )
{
	// Get normalized vector to light
	vec3 toL = Normalize( eslightPos.xyz() );
	
	// Compute a min & max distance between the eyespace light location and the eyespace corners
	//    of the view frustum.  This is constant for the frame and is passed down to the voxelizer
	float nearVal = data->ui->eyeNear, farVal = data->ui->eyeFar;
	float nearVal_y = nearVal*tan( data->ui->eyeFOV * IGLU_PI / 360.0f );
	float nearVal_x = nearVal*tan( data->ui->eyeFOV * IGLU_PI / 360.0f )*data->ui->eyeAspect;

	// Find the view rays to the corners of the screen (which will provide the max and
	//    min (if light is offscreen) tangent distances to the light)
	vec3 c[4];
	c[0] = Normalize( vec3( nearVal_x, nearVal_y, -nearVal ) );  
	c[1] = Normalize( vec3( -nearVal_x, nearVal_y, -nearVal ) );
	c[2] = Normalize( vec3( nearVal_x, -nearVal_y, -nearVal ) );
	c[3] = Normalize( vec3( -nearVal_x, -nearVal_y, -nearVal ) );

	// Find the angles between the light and the corners of the screen
	float minDist = FLT_MAX, maxDist = FLT_MIN;
	for (int i=0; i<4; i++)
	{
		float tmpDist = toL.Dot( c[i] );
		tmpDist = iglu::min( 1.0f, tmpDist );
		tmpDist = acos( iglu::max( -1.0f, tmpDist ) );
		minDist = iglu::min( minDist, tmpDist );
		maxDist = iglu::max( maxDist, tmpDist );
	}
	
	*minDistance = minDist;
	*maxDistance = maxDist;
}

void RectifySMapToEpipolarVoxelBuffer( IGLUFramebuffer::Ptr &voxBuffer, 
									   IGLUShaderProgram::Ptr &rectify, 
									   const vec3 &esLightPos, 
									   float minAngle, float maxAngle )
{
	uint numBufs = voxBuffer->NumColorAttachments();

	voxBuffer->Bind();
	glClearColorIuiEXT(0,0,0,0);
	voxBuffer->Clear();
	glViewport( 0, 0, data->ui->vsvWidth, data->ui->vsvHeight );
	rectify->Enable();
		glEnable( GL_COLOR_LOGIC_OP );
		glLogicOp( GL_OR );
		rectify["minAngle_L_V"]    = 0.0f;
		rectify["maxAngle_L_V"]    = maxAngle;
		rectify["lNear"]           = data->ui->lightNear;
		rectify["lFar"]            = data->ui->lightFar;
		rectify["esLightPos"]      = vec4( esLightPos, 1.0 );
		rectify["numEpipoleLines"] = data->ui->vsvHeight;
		data->vao->garbagePoints->DrawArrays( GL_POINTS, 
			                                  0, 
											  128 * data->ui->vsvHeight * numBufs );
		glDisable( GL_COLOR_LOGIC_OP );
	rectify->Disable();
	voxBuffer->Unbind();
}


void SampleVoxelsShadowMaps::Display( void )
{
	// Compute the eye-space light position (used multiple times per frame)
	vec4 esLightPos = data->mat->eyeView * vec4( data->param->lightPos, 1.0f );
	
	//Render into the shadow map. 
	CreateParaboloidShadowMap( data->fbo->paraboloidShadowMap, esLightPos );

	// Find the angle to bound the epipolar light extents for the current view.
	float minDist, maxDist;
	ReturnMinAndMaxLightAngles( esLightPos, &minDist, &maxDist );

	// Rectify our shadow map into the epipolar voxel space
	RectifySMapToEpipolarVoxelBuffer( data->fbo->epipolarVoxels, 
		                              data->glsl->rectifySMap,  
									  esLightPos.xyz(), 
									  minDist, maxDist );

	// Now count/display our rectified voxels on screen!
	int numBufs = data->fbo->epipolarVoxels->NumColorAttachments();
	data->glsl->countVoxels->Enable();
		data->glsl->countVoxels["intensity"] = data->ui->heatMapIntensity;
		data->glsl->countVoxels["useMRT0"]   = (data->ui->visLayer[0] && numBufs > 0) ? 1 : 0;
		data->glsl->countVoxels["useMRT1"]   = (data->ui->visLayer[1] && numBufs > 1) ? 1 : 0;
		data->glsl->countVoxels["useMRT2"]   = (data->ui->visLayer[2] && numBufs > 2) ? 1 : 0;
		data->glsl->countVoxels["useMRT3"]   = (data->ui->visLayer[3] && numBufs > 3) ? 1 : 0;
		data->vao->textureScreenQuad->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
	data->glsl->countVoxels->Disable();
}


void SampleVoxelsShadowMaps::Initialize( int trackNum )
{
	// Initialize some UI variables for this mode's widget window
	data->ui->visLayer[0].Initialize( true, "Visualize voxels from 1st MRT" );
	data->ui->visLayer[1].Initialize( true, "Visualize voxels from 2nd MRT" );
	data->ui->visLayer[2].Initialize( true, "Visualize voxels from 3rd MRT" );
	data->ui->visLayer[3].Initialize( true, "Visualize voxels from 4th MRT" );
	data->ui->heatMapIntensity.Initialize( 64.0, IGLURange<float>(1.0,256.0), 1.0, "Heat map intensity" );

	// Values to control width & height of VSV. 
	data->ui->vsvWidth.Initialize ( 1024, IGLURange<int>(128,2048), 32, "VSV width (alpha resolution)"); 
	data->ui->vsvHeight.Initialize( 1024, IGLURange<int>(128,2048), 32, "VSV height (theta resolution)");
	data->ui->vsvPadWidth = data->ui->vsvWidth;
	data->ui->vsvPadHeight = data->ui->vsvHeight;
	data->ui->vsvPadCoef  = vec2( data->ui->vsvWidth / float(data->ui->vsvPadWidth), 
		                          data->ui->vsvHeight / float(data->ui->vsvPadHeight) );

	// Add variables to the widget window
	data->win->AddWidget( trackNum, &data->ui->lightMovement, new IGLUVariableCallback( UpdateLightPosition ) );
	data->win->AddWidgetSpacer( trackNum );
	data->win->AddWidget( trackNum, &data->ui->heatMapIntensity );
	data->win->AddWidget( trackNum, &data->ui->visLayer[0] );
	data->win->AddWidget( trackNum, &data->ui->visLayer[1] );
	data->win->AddWidget( trackNum, &data->ui->visLayer[2] );
	data->win->AddWidget( trackNum, &data->ui->visLayer[3] );
	data->win->AddWidgetSpacer( trackNum );
	data->win->AddWidget( trackNum, &data->ui->vsvWidth, new IGLUSimpleCallback( UpdateVSVResolution ) );
	data->win->AddWidget( trackNum, &data->ui->vsvHeight, new IGLUSimpleCallback( UpdateVSVResolution ) );

	// The quad we'll use for the full-screen count pass
	data->vao->textureScreenQuad = IGLUGeometry::CreateQuad( IGLU_GEOM_XY_PLANE | IGLU_GEOM_USE_TEXCOORD );

	// Create our voxel buffer
	data->fbo->epipolarVoxels = IGLUFramebuffer::CreateMRT( GL_RGBA32UI, data->ui->vsvWidth, data->ui->vsvHeight, 4 );

	// Create a dummy vertex array used for our rectification pass.  This is really sort
	//    of silly -- filling up a garbage vertex array so the GPU can ignore it. But it's
	//    easy and straightforward.  The garbage array has 2 floats for each of the 128 bits 
	//    in each pixel along on edge of each of the 4 MRT targets.  This program limits the 
	//    height of the VSV to 4096
	int numGarbagePoints = 8 * 128 * 4096;
	float *garbagePtr = (float *)malloc( sizeof(float) * numGarbagePoints );
	data->vao->garbagePoints = new IGLUVertexArray();
	data->vao->garbagePoints->SetVertexArray( numGarbagePoints * sizeof(float), garbagePtr, IGLU_DYNAMIC|IGLU_STREAM );
	data->vao->garbagePoints->EnableAttribute( IGLU_ATTRIB_VERTEX, 2, GL_FLOAT );
		
	//Load a shader that resamples our shadow map into epipolar space
	data->glsl->rectifySMap = new IGLUShaderProgram( "shaders/rectifySMapToEpipolar.vert.glsl", 
		                                             "shaders/rectifySMapToEpipolar.geom.glsl",
											         "shaders/rectifySMapToEpipolar.frag.glsl" );
	data->glsl->rectifySMap["numLayers"]       = data->fbo->epipolarVoxels->NumColorAttachments();
	data->glsl->rectifySMap["shadowMap"]       = data->fbo->paraboloidShadowMap[ IGLU_DEPTH ];
	data->glsl->rectifySMap["numEpipoleLines"] = data->fbo->epipolarVoxels[0].GetHeight();

	data->glsl->countVoxels = new IGLUShaderProgram( "shaders/countMRTVoxels.vert.glsl", 
											         "shaders/countMRTVoxels.frag.glsl" );
	data->glsl->countVoxels["voxels0"]   = data->fbo->epipolarVoxels[0];
	data->glsl->countVoxels["voxels1"]   = data->fbo->epipolarVoxels[1];
	data->glsl->countVoxels["voxels2"]   = data->fbo->epipolarVoxels[2];
	data->glsl->countVoxels["voxels3"]   = data->fbo->epipolarVoxels[3];
}

