// Provides a helper to setup the LDR glow post-process
//
o3djs.provide( 'patapi.postprocess_glow_ldr' );

o3djs.require( 'patapi' );
o3djs.require( 'patapi.postprocess' );

o3djs.require('o3djs.util');
o3djs.require('o3djs.camera');
o3djs.require('o3djs.math');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.scene');
o3djs.require('o3djs.effect');
o3djs.require('o3djs.primitives');


////////////////////////////////////////////////////////////////////////////
// Declares a class holding informations on the LDR Glow post-process chain
//
//	_O3DClient, the O3D client
//	_O3DPack, the O3D pack into which create the objects
//	_PostProcessChain, the post-process chain where to create the tone-mapper's passes
//	_FinalRenderTargetToToneMap, the final render target that will be tone-mapped and rendered to screen
//	_GlowThreshold, the threshold below which light is too low to add glow contribution
//	_GlowFactor, the factor to apply to the additional glow
//	_optShaderBaseDirectory, an optional base directory for shaders (default is "Data/Shaders/PostProcess Glow LDR/") (don't forget the trailing '/' !)
//
patapi.LDRGlow = function( _O3DClient, _O3DPack, _PostProcessChain, _FinalRenderTargetToApplyGlow, _GlowThreshold, _GlowFactor, _bAntiAlias4X, _optShaderBaseDirectory )
{
	this.m_Client = _O3DClient;	// Save the client for later use
	this.m_Pack = _O3DPack;		// Save the pack for later use
	this.m_PostProcessChain = _PostProcessChain;
	this.m_FinalRenderTarget = _FinalRenderTargetToApplyGlow;
	this.m_optShaderBaseDirectory = _optShaderBaseDirectory ? _optShaderBaseDirectory : "Data/Shaders/PostProcess Glow/";

	this.m_GlowThreshold = _GlowThreshold;
	this.m_GlowFactor = _GlowFactor;


	////////////////////////////////////////////////////////////////////////////
	// Create render targets
	//
	this.m_RenderTargetDownsample2X = null
	this.m_RenderTargetDownsample4X = null
	this.m_RenderTargetDownsample8X = null
	this.m_RenderTargetDownsample8X_2 = null
	try
	{
 		this.m_RenderTargetDownsample2X = new patapi.RenderTarget( this.m_Pack, "Downsample2X Render Target", this.m_Client.width / 2, this.m_Client.height / 2, o3djs.base.o3d.Texture.XRGB8 );
 		this.m_RenderTargetDownsample4X = new patapi.RenderTarget( this.m_Pack, "Downsample4X Render Target", this.m_Client.width / 4, this.m_Client.height / 4, o3djs.base.o3d.Texture.XRGB8 );
 		this.m_RenderTargetDownsample8X = new patapi.RenderTarget( this.m_Pack, "Downsample8X Render Target", this.m_Client.width / 8, this.m_Client.height / 8, o3djs.base.o3d.Texture.XRGB8, o3djs.base.o3d.Sampler.LINEAR );
 		this.m_RenderTargetDownsample8X_2 = new patapi.RenderTarget( this.m_Pack, "Downsample8X_2 Render Target", this.m_Client.width / 8, this.m_Client.height / 8, o3djs.base.o3d.Texture.XRGB8, o3djs.base.o3d.Sampler.LINEAR );
	}
	catch ( e )
	{
		throw "Exception while creating temporary render-target : " + e;
	}


	////////////////////////////////////////////////////////////////////////////
	// Create post-processes passes
	//
	this.m_PostProcessPasses = [];

	var that = this;

	this.m_GlowThresholdParam = null;
	this.m_GlowFactorParam = null;

	var	PassIndex = 0;
	try
	{
		// =========== FIRST PASS : DOWNSAMPLE BY 2 ===========
		PassIndex = 0;

		var PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'PostProcess - DownSampleX2.shader' );
		var	Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "DownsampleX2", [this.m_FinalRenderTarget], this.m_RenderTargetDownsample2X, PostMaterial, SetupPostProcessSamplers_Downsample );
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );

		// =========== SECOND PASS : DOWNSAMPLE BY 4 ===========
		PassIndex = 1;
		PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'PostProcess - DownSampleX2.shader' );
  		Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "DownsampleX4", [this.m_RenderTargetDownsample2X], this.m_RenderTargetDownsample4X, PostMaterial, SetupPostProcessSamplers_Downsample );
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );


		// =========== THIRD PASS : COMPUTE LUMINANCE AND DOWNSAMPLE BY 8 ===========
		PassIndex = 2;

		PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'Downsample + Luminance.shader' );
		Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "Luminance + DownsampleX8", [this.m_RenderTargetDownsample4X], this.m_RenderTargetDownsample8X, PostMaterial, SetupPostProcessSamplers_Downsample );
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );


		// =========== FOURTH & FIFTH PASS: HORIZONTAL THEN VERTICAL GAUSSIAN BLUR ===========
		PassIndex = 3;

		PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'PostProcess - Gauss.shader' );
		Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "Gauss", [this.m_RenderTargetDownsample8X], this.m_RenderTargetDownsample8X_2, PostMaterial, SetupPostProcessSamplers_GaussH );
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );

		PassIndex = 4;

		PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'PostProcess - Gauss.shader' );
		Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "Gauss", [this.m_RenderTargetDownsample8X_2], this.m_RenderTargetDownsample8X, PostMaterial, SetupPostProcessSamplers_GaussV );
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );


		// =========== SIXTH PASS : COMBINE ===========
		PassIndex = 5;

		PostMaterial = this.m_PostProcessChain.CreatePostProcessMaterial( this.m_optShaderBaseDirectory + 'PostProcess - Combine' + (_bAntiAlias4X ? 'AntiAlias4x' : '') + '.shader' );
		Pass = new patapi.PostProcessPass( this.m_Client, this.m_Pack, "Final Combine", [this.m_FinalRenderTarget, this.m_RenderTargetDownsample8X], null, PostMaterial, SetupPostProcessSamplers_Combine );	// Render directly to screen
		this.m_PostProcessChain.BindPass( Pass );
		this.m_PostProcessPasses.push( Pass );
	}
	catch ( e )
	{
		throw "Exception while creating post-process pass #" + PassIndex + " : " + e;
	}



	// Callback used by the post-process chain to setup the post-process passes' samplers
	//	_Pass, the pass to setup
	//	_InputSamplers, the array of input samplers the pass needs
	//	_InputTexCoords, the array of input tex coords to use to sample each sampler
	//
	function SetupPostProcessSamplers_Downsample( _Pass, _InputSamplers, _InputTexCoords )
	{
		var imageParam = _Pass.getMaterial().getParam( 'image' );
			imageParam.value = _InputSamplers[0];

		var	TextureWidth = _InputSamplers[0].texture.width * _InputTexCoords[0][2];
		var	TextureHeight = _InputSamplers[0].texture.height * _InputTexCoords[0][3];

		var	imageTexCoords = _Pass.getMaterial().getParam( 'imageTexCoords' );
			imageTexCoords.value = [_InputTexCoords[0][2], _InputTexCoords[0][3], 2.0 / TextureWidth, 2.0 / TextureHeight];

		var TempThresholdParam = _Pass.getMaterial().getParam( 'LuminanceThreshold' );
		if ( TempThresholdParam != null )
		{
			that.m_GlowThresholdParam = TempThresholdParam;
			that.m_GlowThresholdParam.value = that.m_GlowThreshold;
		};
	}

	function SetupPostProcessSamplers_GaussH( _Pass, _InputSamplers, _InputTexCoords )
	{
		var imageParam = _Pass.getMaterial().getParam( 'image' );
			imageParam.value = _InputSamplers[0];

		var	TextureWidth = _InputSamplers[0].texture.width * _InputTexCoords[0][2];
		var	TextureHeight = _InputSamplers[0].texture.height * _InputTexCoords[0][3];

		var	imageTexCoords = _Pass.getMaterial().getParam( 'imageTexCoords' );
			imageTexCoords.value = [_InputTexCoords[0][2], _InputTexCoords[0][3], 1.0 / TextureWidth, 1.0 / TextureHeight];

		var	Deviation = 2.0;
		var SumWeights = 0.0;
		var Weights = new Array( 8 );
		for ( var i=0; i < 8; i++ )
		{
 			Weights[i] = Math.exp( -i * i / (2 * Deviation * Deviation) ) / (Math.sqrt( 2.0 * Math.PI ) * Deviation);
 			SumWeights += Weights[i]
 		}

		var	ShaderOffset = _Pass.getMaterial().getParam( 'Offset' );
			ShaderOffset.value = [1.0 / TextureWidth, 0.0];	// Only X offset for the horizontal pass

		var	ShaderWeights = _Pass.getMaterial().getParam( 'Weights0' );
			ShaderWeights.value = Weights.slice( 0, 4 );

			ShaderWeights = _Pass.getMaterial().getParam( 'Weights1' );
			ShaderWeights.value = Weights.slice( 4, 8 );
	}

	function SetupPostProcessSamplers_GaussV( _Pass, _InputSamplers, _InputTexCoords )
	{
		var imageParam = _Pass.getMaterial().getParam( 'image' );
			imageParam.value = _InputSamplers[0];

		var	TextureWidth = _InputSamplers[0].texture.width * _InputTexCoords[0][2];
		var	TextureHeight = _InputSamplers[0].texture.height * _InputTexCoords[0][3];

		var	imageTexCoords = _Pass.getMaterial().getParam( 'imageTexCoords' );
			imageTexCoords.value = [_InputTexCoords[0][2], _InputTexCoords[0][3], 1.0 / TextureWidth, 1.0 / TextureHeight];

		var	Deviation = 2.0;
		var SumWeights = 0.0;
		var Weights = new Array( 8 );
		for ( var i=0; i < 8; i++ )
		{
 			Weights[i] = Math.exp( -i * i / (2 * Deviation * Deviation) ) / (Math.sqrt( 2.0 * Math.PI ) * Deviation);
 			SumWeights += Weights[i]
 		}

		var	ShaderOffset = _Pass.getMaterial().getParam( 'Offset' );
			ShaderOffset.value = [0.0, 1.0 / TextureHeight];	// Only Y offset for the vertical pass

		var	ShaderWeights = _Pass.getMaterial().getParam( 'Weights0' );
			ShaderWeights.value = Weights.slice( 0, 4 );

			ShaderWeights = _Pass.getMaterial().getParam( 'Weights1' );
			ShaderWeights.value = Weights.slice( 4, 8 );
	}

	function SetupPostProcessSamplers_Combine( _Pass, _InputSamplers, _InputTexCoords )
	{
		var imageParam = _Pass.getMaterial().getParam( 'image0' );
			imageParam.value = _InputSamplers[0];

		var	TextureWidth = _InputSamplers[0].texture.width * _InputTexCoords[0][2];
		var	TextureHeight = _InputSamplers[0].texture.height * _InputTexCoords[0][3];

		var	imageTexCoords = _Pass.getMaterial().getParam( 'imageTexCoords0' );
			imageTexCoords.value = [_InputTexCoords[0][2], _InputTexCoords[0][3], 1.0 / TextureWidth, 1.0 / TextureHeight];

			imageParam = _Pass.getMaterial().getParam( 'image1' );
			imageParam.value = _InputSamplers[1];

			imageTexCoords = _Pass.getMaterial().getParam( 'imageTexCoords1' );

			TextureWidth = _InputSamplers[1].texture.width * _InputTexCoords[1][2];
			TextureHeight = _InputSamplers[1].texture.height * _InputTexCoords[1][3];
			imageTexCoords.value = [_InputTexCoords[1][2], _InputTexCoords[1][3], 1.0 / TextureWidth, 1.0 / TextureHeight];


		that.m_GlowFactorParam = _Pass.getMaterial().getParam( 'GlowFactor' );
		that.m_GlowFactorParam.value = that.m_GlowFactor;
	}
};

patapi.LDRGlow.prototype = 
{
	// Gets or sets the glow threshold
	//
	getGlowThreshold : function()			{ return this.m_GlowThreshold; },
	setGlowThreshold : function( value )	{ this.m_GlowThreshold = value; this.m_GlowThresholdParam.value = value; },

	// Gets or sets the glow factor
	//
	getGlowFactor : function()				{ return this.m_GlowFactor; },
	setGlowFactor : function( value )		{ this.m_GlowFactor = value; this.m_GlowFactorParam.value = value; },


	// Disposes of used resources
	// Call this when exiting the application
	//
	Dispose : function()
	{
		throw "TODO!"
	}
};
