/*************************************************************************
** Mode_CreateVoxelizedShadowVolumes.cpp                                **
** --------------------------                                           **
**                                                                      **
** Once we have resampled our shadow map (e.g., in the display mode     **
**     Mode_SampleVoxelsFromShadowMap), we can scan along horizontal    **
**     lines (i.e., the alpha-axis) to create our voxelized shadow      **
**     volumes.  This render mode combines all the steps until this     **
**     point, then visualizes the resulting VSV.                        **
**                                                                      **
** Chris Wyman (04/17/2012)                                             **
*************************************************************************/

#include "../projectIncludes.h"
#include "Mode_CreateVoxelizedShadowVolumes.h"


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

// Do a single step of the fast parallel horizontal OR-scan (See GPU Gems 3 article)
void DoScanStep( IGLUFramebuffer::Ptr &outFB,   // Output buffer
				 IGLUFramebuffer::Ptr &inFB,    // Input buffer
				 bool isUpStep )                // If true, doing an up-scan step, else a down-scan
{
	IGLUShaderProgram::Ptr *shader = isUpStep ? &data->glsl->upScan : &data->glsl->downScan;
	int width = isUpStep ? inFB->GetRenderableWidth() : outFB->GetRenderableWidth();

	outFB->Bind();
		(*shader)->Enable();
			(*shader)["voxels0"]   = inFB[ IGLU_COLOR0 ];
			(*shader)["voxels1"]   = inFB[ IGLU_COLOR1 ];
			(*shader)["voxels2"]   = inFB[ IGLU_COLOR2 ];
			(*shader)["voxels3"]   = inFB[ IGLU_COLOR3 ];
			(*shader)["width"]     = width;
			data->vao->textureScreenQuad->DrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
		(*shader)->Disable();
	outFB->Unbind();
}

// Create the voxelized shadow volume by scanning long the horizontal rows.  This is done using
//    a sequence of up-scans, followed by a sequence of down-scans (see a GPU Gems 3 article for
//    technical details).  Since the steps are basically from the CPU's perspective, they are
//    done with a single loop here, with a boolean to the DoScanStep() specifying if it is an
//    up or down scan.  The up scans do not use glEnable( GL_COLOR_LOGIC_OP ), but the down scans
//    do.  The shaders individually specify (in the GLSL) if they want that initialized, we just
//    need to specify the logic op here.
void CreateVSVUsingRectifiedSMap( void )
{
	// Find out how big our buffer is;  vary # scan passes depending on width
	int bufWidth = data->fbo->epipolarVoxels->GetRenderableWidth()/2;
	int mipLevel = 11, mipDelta = 1;

	// Make sure our code can handle this size buffer...
	if ( bufWidth > 2048 || bufWidth < 1 )
		iglu::ErrorExit( "Epipolar voxel buffer too wide/narrow to create voxelized shadow map!\n", 
		                 __FILE__, __FUNCTION__, __LINE__ );

	// Setup (needed for down scans)
	glLogicOp( GL_OR );

	// Do one up scan to prime the pump (and move the epipolar voxelization into the temp buffers)
	DoScanStep( data->fbo->voxelMipScan[mipLevel], data->fbo->epipolarVoxels, true );

	// Do almost all the rest of the up & down scans
	while( mipLevel < 11 || bufWidth > 1 )
	{
		mipLevel -= mipDelta;                // Identify the next temporary to render into
		DoScanStep( data->fbo->voxelMipScan[mipLevel],           // Output of the next scan step
			        data->fbo->voxelMipScan[mipLevel+mipDelta],  // Input to the next scan step
					(mipDelta > 0) );        // True if an 'up' scan.  False if a 'down' scan

		mipDelta = ((bufWidth/=2) <= 1) ? -1 : 1;  // After enough up scans (log2(width)), start going down
	}

	// Do one final down scan to move the final result back into the epipolar voxel buffer
	DoScanStep( data->fbo->epipolarVoxels, data->fbo->voxelMipScan[11], false );
}


void CreateVoxelizedShadowVolumes::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 );

	// Create our voxelized shadow map!
	CreateVSVUsingRectifiedSMap();

	// 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 CreateVoxelizedShadowVolumes::Initialize( int trackNum )
{
	// 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 ) );
		
	// Initialize as many intermediate FBOs as we need, based on the size
	//    of the epipolar voxel buffer (to a max of 2^12)
	int nextBufWidth  = data->fbo->epipolarVoxels->GetRenderableWidth();
	int nextBufHeight = data->fbo->epipolarVoxels->GetRenderableHeight();
	for (int i=11; i>=0; i--)
	{
		data->fbo->voxelMipScan[i] = IGLUFramebuffer::CreateMRT( GL_RGBA32UI, 
			                                                     nextBufWidth, nextBufHeight,
																 data->fbo->epipolarVoxels->NumColorAttachments() );
		if (nextBufWidth > 1) 
			nextBufWidth /= 2;
	}

	data->glsl->upScan   = new IGLUShaderProgram( "shaders/upAndDownScans.vert.glsl",
									  		      "shaders/upScan.frag.glsl" );
	data->glsl->downScan = new IGLUShaderProgram( "shaders/upAndDownScans.vert.glsl",
									  		      "shaders/downScan.frag.glsl" );
}

