// This hosts a water plane representation : a giant plane at altitude 0 stuck to the camera
//
o3djs.provide( 'patapi.waterplane' );

o3djs.require( 'patapi' );

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');

var Math3D = o3djs.math;	// O3D Math


////////////////////////////////////////////////////////////////////////////
// Declares a class holding informations on the water plane
//
//	_O3DClient, the O3D client
//	_O3DPack, the O3D pack into which create the objects
//	_EffectsManager, the effects manager
//	_SubdivisionsCount, the amount of plane subdivisions
//	_optWaterTransportColor, the optional transport color of water, a [R,G,B] vector (i.e. the internal color of the water the light will get colored with while circulating underwater)
//	_optAbsorptionCoefficient, the optional extinction coefficient
//	_optScatteringCoefficient, the optional scattering coefficient
//	_optScatteringAnisotropy, the optional anisotropy of scattering events (-1 is backward, +1 is forward)
//	_optScrollingVelocity, the optional scrolling velocity (a [X,Z] 2D vector)
//
patapi.WaterPlane = function( _O3DClient, _O3DPack, _EffectsManager, _SubdivisionsCount, _optWaterTransportColor, _optAbsorptionCoefficient, _optScatteringCoefficient, _optScatteringAnisotropy, _optScrollingVelocity )
{
	this.m_Client = _O3DClient;	// Save the client for later use
	this.m_Pack = _O3DPack;		// Save the pack for later use

	this.m_WaterTransportColor = !!_optWaterTransportColor ? _optWaterTransportColor : [0.1, 0.6, 0.9];
	this.m_AbsorptionCoefficient = !!_optAbsorptionCoefficient ? _optAbsorptionCoefficient : 0.010;	// 0.040; // More murky water => fast extinction
	this.m_ScatteringCoefficient = !!_optScatteringCoefficient ? _optScatteringCoefficient : 0.010;	// 0.020;
	this.m_ScatteringAnisotropy = !!_optScatteringAnisotropy ? _optScatteringAnisotropy : 0.9;
	this.m_ScrollingVelocity = !!_optScrollingVelocity ? _optScrollingVelocity : [0.1, 0.03]
	this.m_NormalSamplerLF = null;
	this.m_NormalSamplerHF = null;
	this.m_SkyCubeMapSampler = null;


	////////////////////////////////////////////////////////////////////////////
	// Register ourselves to the Effects Manager as a provider for the "Water" shader interface
	var	that = this;
	this.m_ShaderDataProvider = _EffectsManager.CreateInterfaceDataProvider( "Water Plane Data Provider", "Water", patapi.EFFECTS_MANAGER_PROVIDER_TYPE.INIT, function( _Object )
	{
		_Object.getParam( 'WaterTransportColor' ).value = [1.0-that.m_WaterTransportColor[0], 1.0-that.m_WaterTransportColor[1], 1.0-that.m_WaterTransportColor[2]];
//		_Object.getParam( 'WaterTransportColor' ).value = that.m_WaterTransportColor;
		_Object.getParam( 'WaterCoefficients' ).value = [that.m_AbsorptionCoefficient, that.m_ScatteringCoefficient, that.m_ScatteringAnisotropy, 0.0];
		_Object.getParam( 'WaterNormalLF' ).value = that.m_NormalSamplerLF;
		_Object.getParam( 'WaterNormalHF' ).value = that.m_NormalSamplerHF;
		_Object.getParam( 'SkyCubeMap' ).value = that.m_SkyCubeMapSampler;
		_Object.getParam( 'NormalScrollingVelocity' ).value = [that.m_ScrollingVelocity[0], that.m_ScrollingVelocity[1], that.m_ScrollingVelocity[0], that.m_ScrollingVelocity[1]];
	} );


	////////////////////////////////////////////////////////////////////////////
	// Create the plane material using the water shader
	//
	this.m_Material = _EffectsManager.CreateMaterial( "#Water Material", "Data/Shaders/Water/WaterOver.shader" );		// <== Prefix by # so it's not overwritten by the ApplyShader function or material translation


	////////////////////////////////////////////////////////////////////////////
	// Create the plane geometry
	//
	var	PlaneSize = 1000.0 * Math.SQRT2;	// So the plane seems to go until far clip limit

	var vertexInfo = o3djs.primitives.createVertexInfo();
	var PositionStream = vertexInfo.addStream( 3, o3djs.base.o3d.Stream.POSITION );

// 	// ===== Build the vertices =====
// 	PositionStream.addElementVector( [-PlaneSize, 0.0, -PlaneSize] );
// 	PositionStream.addElementVector( [-PlaneSize, 0.0, +PlaneSize] );
// 	PositionStream.addElementVector( [+PlaneSize, 0.0, +PlaneSize] );
// 	PositionStream.addElementVector( [+PlaneSize, 0.0, -PlaneSize] );
// 
// 	// ===== Build the indices =====
// 	vertexInfo.addTriangle(	0, 1, 2 );
// 	vertexInfo.addTriangle(	0, 2, 3 );


 	// ===== Build the vertices =====
 	var	SubdivsCount = 1 + _SubdivisionsCount;
 	for ( var X=0; X <= SubdivsCount; X++ )
 	{
 		var PosX = PlaneSize * ((2.0 * X / SubdivsCount) - 1.0);
		for ( var Z=0; Z <= SubdivsCount; Z++ )
		{
	 		var PosZ = PlaneSize * ((2.0 * Z / SubdivsCount) - 1.0);
			PositionStream.addElementVector( [PosX, 0.0, PosZ] );
		}
	}

 	// ===== Build the indices =====
 	for ( var X=0; X < SubdivsCount; X++ )
 	{
		for ( var Z=0; Z < SubdivsCount; Z++ )
		{
 			vertexInfo.addTriangle(	(SubdivsCount+1) * (Z+0) + (X+0),
 									(SubdivsCount+1) * (Z+1) + (X+1),
 									(SubdivsCount+1) * (Z+1) + (X+0) );

 			vertexInfo.addTriangle(	(SubdivsCount+1) * (Z+0) + (X+0),
 									(SubdivsCount+1) * (Z+0) + (X+1),
 									(SubdivsCount+1) * (Z+1) + (X+1) );
		}
	}

	this.m_PlaneShape = vertexInfo.createShape( this.m_Pack, this.m_Material );
	this.m_PlaneShape.name = "WaterPlane Shape";

	this.m_PlaneTransform = this.m_Pack.createObject( 'Transform' );	// Create a Root Transform for the water plane scene
	this.m_PlaneTransform.name = "WaterPlane";
	this.m_PlaneTransform.addShape( this.m_PlaneShape );				// Attach the shape to the scene.
	this.m_PlaneTransform.cull = false;

//alert( patapi.helpers.EnumerateProperties( this.m_PlaneTransform ) )


	////////////////////////////////////////////////////////////////////////////
	// Create the render nodes
	//

	// Create our single state-set
	// The state-set will be the root of every drawpasses, one drawpass per post-process
	var State = this.m_Pack.createObject( 'State' );
		State.getStateParam( 'o3d.ZWriteEnable' ).value = false;
//		State.getStateParam( 'o3d.ZWriteEnable' ).value = false;	//o3djs.base.o3d.State.CMP_ALWAYS;	// Always authorize writing (so the shader can be used to initialize the ZBuffer)

//this.m_Material.state = State;

	this.m_StateSet = this.m_Pack.createObject( 'StateSet' );
	this.m_StateSet.name = "WaterPlane StateSet";
	this.m_StateSet.state = State;

	// Create our single draw-list
	this.m_DrawList = this.m_Pack.createObject( 'DrawList' );
	this.m_DrawList.name = "WaterPlane DrawList";

	// Create a tree-traversal render node bound to our single object
	this.m_TreeTraversal = this.m_Pack.createObject( 'TreeTraversal' );
	this.m_TreeTraversal.name = "WaterPlane TreeTraversal";
	this.m_TreeTraversal.transform = this.m_PlaneTransform;								// Tie to the transform as it's the only object we're going to render
	this.m_TreeTraversal.parent = this.m_StateSet;
	this.m_TreeTraversal.priority = 0;

	// Create a single drawpass for that post-process
	this.m_DrawPass = this.m_Pack.createObject( 'DrawPass' );
	this.m_DrawPass.name = "WaterPlane DrawPass";
	this.m_DrawPass.drawList = this.m_DrawList;
	this.m_DrawPass.parent = this.m_StateSet;
	this.m_DrawPass.priority = 1;

	// Assign the material's draw list
	this.m_Material.drawList = this.m_DrawList;
};

patapi.WaterPlane.prototype = 
{
	// Gets or sets the water transport color
	getWaterTransportColor : function()			{ return this.m_WaterTransportColor; },
	setWaterTransportColor : function( value )
	{
		this.m_WaterTransportColor = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the water transport color
	getAbsorptionCoefficient : function()		{ return this.m_AbsorptionCoefficient; },
	setAbsorptionCoefficient : function( value )
	{
		this.m_AbsorptionCoefficient = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the water transport color
	getScatteringCoefficient : function()		{ return this.m_ScatteringCoefficient; },
	setScatteringCoefficient : function( value )
	{
		this.m_ScatteringCoefficient = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the water transport color
	getScatteringAnisotropy : function()		{ return this.m_ScatteringAnisotropy; },
	setScatteringAnisotropy : function( value )
	{
		this.m_ScatteringAnisotropy = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the low frequency normal texture
	getNormalSamplerLF : function()				{ return this.m_NormalSamplerLF; },
	setNormalSamplerLF : function( value )
	{
		this.m_NormalSamplerLF = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the high frequency normal texture
	getNormalSamplerHF : function()				{ return this.m_NormalSamplerHF; },
	setNormalSamplerHF : function( value )
	{
		this.m_NormalSamplerHF = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the high frequency normal texture
	getSkyCubeMapSampler : function()			{ return this.m_SkyCubeMapSampler; },
	setSkyCubeMapSampler : function( value )
	{
		this.m_SkyCubeMapSampler = value;

		this.Update();		// Update the shader constants
	},

	// Gets or sets the visible state of the plane
	getVisible : function()						{ return this.m_PlaneTransform.visible; },
	setVisible : function( value )				{ this.m_PlaneTransform.visible = value; },

	// Disposes of used resources
	// Call this when exiting the application
	//
	Dispose : function()
	{
		throw "TODO!"
	},

	// Binds the water plane to the scene
	//	_SceneViewInfos, the standard ViewInfo structure created by a call to o3djs.rendergraph.createView()
	//
	BindToScene : function(	_SceneViewInfos )
	{
		if ( _SceneViewInfos == null || typeof(_SceneViewInfos) == typeof(undefined) )
			throw "Invalid SceneViewInfo !";

		this.m_SceneViewInfos = _SceneViewInfos;

		// Simply add the material to the draw list
		this.m_Material.drawList = _SceneViewInfos.zOrderedDrawList;
//		this.m_Material.drawList = _SceneViewInfos.performanceDrawList;

		// And add the shape's transform to the root transform graph
		this.m_PlaneTransform.parent = _SceneViewInfos.treeTraversal.transform;

/*
		// We're assuming the created render graph is something like:
		//
		//        [Viewport]
		//            |
		//     +------+--------+------------------+---------------------+
		//     |               |                  |                     |
		// [ClearBuffer] [TreeTraversal] [Performance StateSet] [ZOrdered StateSet]
		//                                        |                     |
		//                               [Performance DrawPass] [ZOrdered DrawPass]
		//
		//
		// Modify it a bit so it doesn't call the "ClearBuffer" render node but renders our water plane instead
		//

		this.m_StateSet.parent = this.m_SceneViewInfos.root;
		this.m_StateSet.priority = this.m_SceneViewInfos.clearBuffer.priority;
		this.m_TreeTraversal.registerDrawList( this.m_DrawList, this.m_SceneViewInfos.drawContext, true );	// Tie to our only draw list

		// Detach the clear buffer node
		this.m_SceneViewInfos.clearBuffer.parent = null;

		// We should now have something more like:
		//
		//                   [Viewport]
		//                       |
		//                +------+--------+------------------+---------------------+
		//                |               |                  |                     |
		//          [SkyStateSet]   [TreeTraversal] [Performance StateSet] [ZOrdered StateSet]
		//                |                                  |                     |
		//        +-------+-------+               [Performance DrawPass]   [ZOrdered DrawPass]
		//        |               |
		// [TreeTraversal]   [DrawPass]
*/
	},

	// Sets the time used by the shaders
	//
	SetTime : function( _Time )
	{
		this.m_Material.getParam( 'GlobalTime' ).value = _Time;
	},

	// Updates the water plane parameters given the current sun direction and ambient turbidity (avoid values below 2!)
	//
	Update : function()
	{
		// Simply provide data to all materials needing them
		this.m_ShaderDataProvider.ProvideDataAll();
	}
};
