/*************************************************************************
** 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_ParaboloidShadowMaps.h"

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

// A function to create a dual-parabolid shadow map into a given buffer, from a specified eye-space light point
void CreateParaboloidShadowMap( IGLUFramebuffer::Ptr &smap, const vec4 &esLightPos )
{
	//Render into the shadow map.  
	smap->Bind();       // Bind shadow map
	smap->Clear();      // Clear shadow map
		data->glsl->parabSMapCreate->Enable();    // Start drawing to shadow map (w/shader)
		    data->glsl->parabSMapCreate["eyeView"]    = data->mat->eyeView;   // Set some shader vars
		    data->glsl->parabSMapCreate["esLightPos"] = esLightPos;
			data->glsl->parabSMapCreate["model"]      = data->mat->cowPosition * data->ui->ball->GetMatrix();
			data->geom->cow->Draw( data->glsl->parabSMapCreate );             // Draw geometry
		data->glsl->parabSMapCreate->Disable();   // Done drawing to shadow map
	smap->Unbind();     // Unbind shadow map
}


// Draws our scene using shadow maps (creating using a parabolic projection)
void RenderParaboloidShadowMaps::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 );
	
	// OK.  Now draw the scene for real!
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Setup some shader variable values constant over all the geometry in the frame
	data->glsl->parabSMapQuery["useLambertian"] = data->ui->useLambertian ? 1.0f : 0.0f;
	data->glsl->parabSMapQuery["esLightPos"]    = esLightPos;
	data->glsl->parabSMapQuery["view"]          = data->mat->eyeView;
	data->glsl->parabSMapQuery["sMapBias"]      = data->ui->zBias;
	
	// Draw the geometry, modifying per-geometry shader variables as needed
	data->glsl->parabSMapQuery->Enable();
		data->glsl->parabSMapQuery["model"] = data->mat->cowPosition * data->ui->ball->GetMatrix();
		data->geom->cow->Draw( data->glsl->parabSMapQuery );         // Draw cow geometry
		data->glsl->parabSMapQuery["model"] = IGLUMatrix4x4::Identity();
		data->geom->cornellBox->Draw( data->glsl->parabSMapQuery );  // Draw Cornell Box geometry
	data->glsl->parabSMapQuery->Disable();
}


// Initialization required for our simple shadow map rendering approach
void RenderParaboloidShadowMaps::Initialize( int trackNum )
{
	// Initialized user-interface variables
	data->ui->visualizeShadowmap.Initialize( false, "Visualize shadowmap" );
	data->ui->useLambertian.Initialize     ( true,  "Use Lambertian shading" );
	data->ui->zBias.Initialize             ( -0.001, IGLURange<float>(-0.02,0), 0.0001, "Shadow map bias" );
	data->ui->smapRes.Initialize           ( 1024,   IGLURange<int>(128,4096),  1,      "Shadow map resolution" );
	data->ui->lightMovement.Initialize     ( 3.5,    IGLURange<float>(0,4), 0.04,       "Light movement" );
	data->ui->lightNear.Initialize         ( 0.1,    IGLURange<float>(0.1,3),  0.1,     "Light near plane" );
	data->ui->lightFar.Initialize          ( 9.3,    IGLURange<float>(5.0,10), 0.1,     "Light far plane" );
	data->ui->visualizeLayer.Initialize    ( 0,      IGLURange<int>(0,1),      1,       "Displayed Shadow Map Layer" );

	// Variables that *could* be user-controlled, but for now may not be.  Needed for other rendering variables
	data->ui->eyeFOV.Initialize( 38.5, IGLURange<float>(15.0,120.0), 0.1, "Eye field-of-view");
	data->ui->eyeNear.Initialize( 5.0, IGLURange<float>(0.1,8.0),    0.1, "Eye near plane");
	data->ui->eyeFar.Initialize( 20.0, IGLURange<float>(5.0,30.0),   0.1, "Eye far plane");
	data->ui->eyeAspect.Initialize( data->win->w() / float(data->win->h()), IGLURange<float>(0.1,3), 0.1, "Display aspect ratio");

	// Setup some matrices used during rendering
	data->mat->cowPosition = IGLUMatrix4x4::Translate( 2.78, 1.2, 2.795 ) * IGLUMatrix4x4::Scale( 2.0f ) * 
                             IGLUMatrix4x4::Rotate( -45, vec3::YAxis() );
	data->mat->eyeProj     = IGLUMatrix4x4::Perspective( data->ui->eyeFOV, data->ui->eyeAspect, data->ui->eyeNear, data->ui->eyeFar );
	data->mat->eyeView     = IGLUMatrix4x4::LookAt( data->param->eyePos, 
												    vec3(2.78, 2.73, 2.795), 
												    vec3::YAxis() );

	// Create a 2D array shadow map with 2 layers
	data->fbo->paraboloidShadowMap = IGLUFramebuffer::CreateArray( IGLU_FBO_SHADOW_MAP, data->ui->smapRes, data->ui->smapRes, 2 );
	
	//Load a shader that creates a shadow map
	data->glsl->parabSMapCreate = new IGLUShaderProgram( "shaders/paraboloidShadowMapTransform.vert.glsl", 
		                                                 "shaders/paraboloidShadowMapTransform.geom.glsl",
											             "shaders/paraboloidShadowMapTransform.frag.glsl" );
	data->glsl->parabSMapCreate["nearDist"]   = data->ui->lightNear;
	data->glsl->parabSMapCreate["farDist"]    = data->ui->lightFar;
	
	// Load a shader that queries a shadow map
	data->glsl->parabSMapQuery = new IGLUShaderProgram("Shaders/test.vert.glsl", "Shaders/query-shadowmap.frag.glsl");
	data->glsl->parabSMapQuery["shadowTex"]   = data->fbo->paraboloidShadowMap[ IGLU_DEPTH ];
	data->glsl->parabSMapQuery["nearDist"]    = data->ui->lightNear;
	data->glsl->parabSMapQuery["farDist"]     = data->ui->lightFar;
	data->glsl->parabSMapQuery["proj"]        = data->mat->eyeProj;
	data->glsl->parabSMapQuery["sMapBias"]    = data->ui->zBias;
	data->glsl->parabSMapQuery["matlInfoTex"] = IGLUOBJMaterialReader::s_matlCoefBuf;

	//Load a shader that visualizes the shadow map
	data->glsl->parabSMapDisplay = new IGLUShaderProgram( "shaders/displayShadowArray.vert.glsl",
									  		              "shaders/displayShadowArray.frag.glsl" );
	data->glsl->parabSMapDisplay["layer"]     = float(data->ui->visualizeLayer.GetValue());	

	// Add widgets to the built-in UI window.  Also setup callbacks (if needed)
	data->win->AddWidget      ( trackNum, &data->ui->zBias );
	data->win->AddWidget      ( trackNum, &data->ui->smapRes,       data->fbo->paraboloidShadowMap->GetResizeCallback() ); 
	data->win->AddWidgetSpacer( trackNum );
	data->win->AddWidget      ( trackNum, &data->ui->lightMovement, new IGLUVariableCallback( UpdateLightPosition ) );
	data->win->AddWidget      ( trackNum, &data->ui->useLambertian );
}

