/*
	Trampoline Sim.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		This object represents the trampoline in the game.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects/GameTrampoline.h"
#include "Game/GameCore.h"
#include "Physics/GridMeshSoftBody.h"
#include "Physics/ShapeHeightfield.h"
#include "Graphics/Camera3d.h"
#include "Physics/SimulationWorld.h"

using namespace Game;

//=====================================================================================================================
// Constants
//=====================================================================================================================

/// Scale of the trampoline texture

const Vec2f GameTrampoline::TEXTURE_SCALE = Vec2f( 2.0f , 2.0f );

//=====================================================================================================================
// Statics
//=====================================================================================================================

#ifdef USE_DEBUG_CONSOLE

	/// True if to draw normals
	
	bool GameTrampoline::s_draw_normals = false;

#endif

//=====================================================================================================================
/// @brief		Creates the trampoline with the specified position and xz size and number of squares
/// @param		position - position to make at
/// @param		xz_size - size of the trampoline on xz axes
/// @param		num_squares - number of trampoline squares on xz axes
/// @param		mass - mass of the trampoline
/// @pre		xz_size must be > 0 on all axes
/// @pre		num_squares must be >= 1
/// @pre		mass must be > 0
//=====================================================================================================================
				
GameTrampoline::GameTrampoline( const Vec3f & position , const Vec2f & xz_size , unsigned num_squares , float mass )
{
	// Make sure ok:
	
	ENSURE( xz_size.x > 0		, "xz_size must be > 0 on all axes" )
	ENSURE( xz_size.y > 0		, "xz_size must be > 0 on all axes" )
	ENSURE( mass > 0			, "mass must be > 0"				)
	ENSURE( num_squares > 0		, "num_squares must be >= 1"		)
	
	// Create our graphics resources
	
	m_ps		= GameCore::GetSingleton()->GetPixelShaderManager()->GetPixelShader("Shaders\\Trampoline.psh.bin");
	m_vs		= GameCore::GetSingleton()->GetVertexShaderManager()->GetVertexShader("Shaders\\Trampoline.vsh.bin");
	m_texture	= GameCore::GetSingleton()->GetTextureManager()->GetTexture("Textures\\Trampoline.dds");
	
	// Create vertex format
	
	{
		VertexFormat::ElementType elements[] = 
		{
			VertexFormat::VF_TYPE_FLOAT3	,
			VertexFormat::VF_TYPE_FLOAT2	,
			VertexFormat::VF_TYPE_FLOAT3
		};
		
		VertexFormat::ElementUsage usages[] = 
		{
			VertexFormat::VF_USAGE_POSITION	,
			VertexFormat::VF_USAGE_TEXCOORD ,
			VertexFormat::VF_USAGE_NORMAL
		};
	
		m_vf = VertexFormat
		(
			GameCore::GetSingleton()->GetDisplay()	,
			3										,
			usages									,
			elements
		);	
	}
	
	// Assume invalid graphics resources
	
	m_valid_resources = false;
	
	// Make sure all are valid:
	
	if ( m_ps.IsValid() && m_vs.IsValid() && m_vf.IsValid() && m_texture.IsValid() && m_vf.IsValid() )
	{
		// Load these uniforms:
		
		m_u_world_transform				= m_vs.GetUniform("World");
		m_u_view_projection_transform	= m_vs.GetUniform("ViewProjection");
		m_u_light_position				= m_ps.GetUniform("LightPosition");
		m_u_camera_position				= m_ps.GetUniform("CameraPosition");
		
		// Only if valid:
		
		if 
		( 
			m_u_world_transform.IsValid()			&&
			m_u_view_projection_transform.IsValid()	&&
			m_u_light_position.IsValid()			&& 
			m_u_camera_position.IsValid() 
		)
		{
			m_valid_resources = true;
		}
	}
	
	// Debug only:
	
	#ifdef USE_DEBUG_CONSOLE
	
		if ( m_valid_resources )
		{
			// Create the normal vertex format
			
			VertexFormat::ElementType	elements[] = { VertexFormat::VF_TYPE_FLOAT3		};
			VertexFormat::ElementUsage	usages[]   = { VertexFormat::VF_USAGE_POSITION	};
		
			m_vf_normals = VertexFormat
			(
				GameCore::GetSingleton()->GetDisplay()	,
				1										,
				usages									,
				elements
			);
			
			// Vertex and pixel shaders:
			
			m_vs_normals = GameCore::GetSingleton()->GetVertexShaderManager()->GetVertexShader("Shaders\\Normals.vsh.bin");
			m_ps_normals = GameCore::GetSingleton()->GetPixelShaderManager()->GetPixelShader("Shaders\\Normals.psh.bin");
			
			// Only if valid:
			
			if ( m_vf_normals.IsValid() && m_vs_normals.IsValid() && m_ps_normals.IsValid() )
			{
				// Get transform:
				
				m_u_normals_transform = m_vs_normals.GetUniform("Transform");
				
				// Only if valid:
				
				if ( m_u_normals_transform.IsValid() )
				{
					m_valid_resources = m_valid_resources && true;
				}
			}
			else
			{
				m_valid_resources = false;
			}
		}
	
	#endif
	
	// Physics:
	
	ShapeHeightfield * heightfield = new ShapeHeightfield
	(
		num_squares			,		// number of squares in the heightfield
		xz_size				,		// xz scale of the heightfield
		position					// position of the heightfield
	);
	
	m_body = new GridMeshSoftBody(heightfield,mass);
	
	// Add to the world:
	
	GameCore::GetSingleton()->GetSimulationWorld()->Add(m_body);	
	
	// Save the number of squares:
	
	m_num_squares = num_squares;	
	
	// Create vertex array:
	
	m_va = new Vertex[ ( m_num_squares + 1 ) * ( m_num_squares * 1 ) * 2 ];
	
	// Debug only: normals vertex array:
	
	#ifdef USE_DEBUG_CONSOLE
	
		m_va_normals = new NormalVertex[ ( m_num_squares + 1 ) * ( m_num_squares + 1 ) * 2 ];
	
	#endif
	
	// Create index array:
	
	m_ia = new unsigned[ m_num_squares * m_num_squares * 6 ];
	
	// Fill the index array:
	
	FillIndexArray();
	
	// Console commands:
	
	#ifdef USE_DEBUG_CONSOLE
	
		DebugConsole::AddCommand( "P_DrawTrampolineNormals" , ConCmd_P_DrawTrampolineNormals );
	
	#endif
}

//=====================================================================================================================
/// @brief		Destroys the trampoline
//=====================================================================================================================

GameTrampoline::~GameTrampoline()
{ 
	// Remove from the world:
	
	GameCore::GetSingleton()->GetSimulationWorld()->Remove(m_body);	
	
	// Done:

	delete m_body; 
	
	// Get rid of graphics resources
	
	delete[] m_ia;
	delete[] m_va;
	
	// Console commands:
	
	#ifdef USE_DEBUG_CONSOLE
	
		DebugConsole::RemoveCommand( "P_DrawTrampolineNormals" );
		
		// Remove normals vertex array:
		
		delete[] m_va_normals;
	
	#endif	
}

//=====================================================================================================================
/// @brief		Renders the trampoline
/// @param		c - camera to render from
/// @pre		Given camera cannot be null
//=====================================================================================================================
				
void GameTrampoline::OnDraw( Camera3D * c )
{
	// Make sure ok
	
	ENSURE( c != 0 , "Given camera cannot be null" )

	// Only if we have valid resources
	
	if ( m_valid_resources == false ) return;
	
	// Fill the vertex array:
	
	FillVertexArray();
	
	// Setup vertex and pixel shaders
	
	m_vs.Apply();
	m_ps.Apply();
	m_vf.Apply();
	
	// Set texture
	
	m_texture.Apply(0);
	
	// Get the scale of the body:
	
	Vec2f xz_scale = ((ShapeHeightfield&) m_body->GetShape()).GetXZScale();
	
	// Set shader uniforms:
	
	m_u_world_transform.SetMatrix
	(
		Matrix4f::Scale( Vec3f(xz_scale.x,1,xz_scale.y) )	*
		m_body->GetWorldOrientation().ToMatrix4()			* 
		Matrix4f::Translation(m_body->GetWorldPosition())	
	);
	
	m_u_view_projection_transform.SetMatrix( c->GetViewProjectionMatrix() );
	
	//m_u_light_position.SetVec3f(Vec3f::UnitY()*32);
	
	m_u_light_position.SetVec3f(c->GetPosition());
	m_u_camera_position.SetVec3f(c->GetPosition());
	
	// Draw the trampoiline:
	
	GameCore::GetSingleton()->GetDisplay()->GetDevice()->DrawIndexedPrimitiveUP
	(
		D3DPT_TRIANGLELIST								,
		0												,	// minimum vertex index
		( m_num_squares + 1 ) * ( m_num_squares + 1 )	,	// vertices used
		m_num_squares * m_num_squares * 2				,	// primitive count
		m_ia											,	// index array
		D3DFMT_INDEX32									,	// index format
		m_va											,	// vertex array
		m_vf.GetVertexSize()								// vertex stride
	);
	
	#ifdef USE_DEBUG_CONSOLE
	
		// See if to draw normals:
		
		if ( s_draw_normals )
		{
			// Get the heightfield
			
			ShapeHeightfield & heightfield = (ShapeHeightfield&)(m_body->GetShape());
		
			// Need to fill the normals array:
			
			unsigned count = ( m_num_squares + 1 ) * ( m_num_squares + 1 );
			
			for ( unsigned i = 0 ; i < count ; ++i )
			{
				// Get the point and normal:
			
				Vec3f p = m_va[i].m_position;
				Vec3f n = m_va[i].m_normal;
				
				// Scale and position the point:
				
				p.x *= xz_scale.x;
				p.z *= xz_scale.y;		
				p	+= m_body->GetWorldPosition();
				
				// Resample the height to test height sampling:
				
				float height = heightfield.SampleHeight
				(
					Vec2f(p.x,p.z)
				);
				
				p.y = height;				
				
				// Put into the va
			
				m_va_normals[ i * 2 + 0 ].m_position = p;
				m_va_normals[ i * 2 + 1 ].m_position = p + n * 2.5f;
			}
			
			// Get ready to draw normals:
			
			m_vs_normals.Apply();
			m_ps_normals.Apply();
			m_vf_normals.Apply();
			
			m_u_normals_transform.SetMatrix(c->GetViewProjectionMatrix());
			
			// Draw:
			
			GameCore::GetSingleton()->GetDisplay()->GetDevice()->DrawPrimitiveUP
			(
				D3DPT_LINELIST									,
				( m_num_squares + 1 ) * ( m_num_squares + 1 )	,	// Primitive count
				m_va_normals									,	// va
				m_vf_normals.GetVertexSize()						// stride
			);
		}	
	
	#endif
}

//=====================================================================================================================
/// @brief		Fills the index array: this only needs to be done once
//=====================================================================================================================

void GameTrampoline::FillIndexArray()
{
	// Current pointer in the index array
	
	unsigned * index = m_ia;
	
	// Do all the triangles:
	
	for ( unsigned r = 0 ; r < m_num_squares ; ++r )
	{
		for ( unsigned c = 0 ; c < m_num_squares ; ++c )
		{
			// Fill indices for this square:
			
			index[0]	= ( ( r     ) * ( m_num_squares + 1 ) ) + ( c     );
			index[1]	= ( ( r     ) * ( m_num_squares + 1 ) ) + ( c + 1 );
			index[2]	= ( ( r + 1 ) * ( m_num_squares + 1 ) ) + ( c + 1 );
			index[3]	= ( ( r + 1 ) * ( m_num_squares + 1 ) ) + ( c + 1 );
			index[4]	= ( ( r + 1 ) * ( m_num_squares + 1 ) ) + ( c     );
			index[5]	= ( ( r     ) * ( m_num_squares + 1 ) ) + ( c     );		
			
			// Next two triangles:
			
			index += 6;
		}
	}
}

//=====================================================================================================================
/// @brief		Fills the vertex array: this only needs to be done once
//=====================================================================================================================

void GameTrampoline::FillVertexArray()
{
	// Get the heightfield
	
	ShapeHeightfield & heightfield = (ShapeHeightfield&)(m_body->GetShape());

	// Get the vertex array for the trampoline:
	
	const Vec3f * va = heightfield.GetVertices();
	
	// Position of the trampoline in 2d:
	
	Vec2f t_pos_2d = Vec2f
	(
		heightfield.GetPosition().x,
		heightfield.GetPosition().z
	);
	
	// Fill in our vertex array:
	
	unsigned count = 0; unsigned squares_plus_1 = m_num_squares + 1;
	
	// Do it:
	
	for ( unsigned r = 0 ; r < squares_plus_1 ; ++r )
	{
		for ( unsigned c = 0 ; c < squares_plus_1 ; ++c )
		{					
			// Set vertex position:
			
			m_va[count].m_position = va[count];
			
			// Figure out the position to sample the normal at:
			
			Vec2f p = Vec2f
			(
				m_va[count].m_position.x,
				m_va[count].m_position.z
			);
			
			p.x *= heightfield.GetXZScale().x;
			p.y *= heightfield.GetXZScale().y;
			p.x += heightfield.GetPosition().x;
			p.y += heightfield.GetPosition().z;
			
			// Sample normal:
			
			m_va[count].m_normal = heightfield.SampleNormal(p);
			
			// Texture coordinate
			
			m_va[count].m_texcoord = Vec2f
			(
				( float(c) / float(m_num_squares) ) * TEXTURE_SCALE.x ,
				( float(r) / float(m_num_squares) ) * TEXTURE_SCALE.y
			);
			
			// Next vertex
			
			count++;
			
			// Make sure ok:
			
			ENSURE( count <= ( m_num_squares + 1 ) * ( m_num_squares + 1 ) , "Vertex array must not be overrun" )
		}
	}
}

#ifdef USE_DEBUG_CONSOLE

//=====================================================================================================================
/// @brief		Console command: enables drawing of normals
/// @param		arg - argument
/// @param		arg_given - if an argument was given
//=====================================================================================================================

bool GameTrampoline::ConCmd_P_DrawTrampolineNormals( bool arg  , bool arg_given )
{
	if ( arg_given ) s_draw_normals = arg; return s_draw_normals;
}

#endif