/*
	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 class represents a box. It renders the box as well as holding the boxe's 
///				RigidBody and shape.
//=====================================================================================================================

#include "Precompiled.h"
#include "Game/GameCore.h"
#include "GameObjects/GameBox.h"
#include "GameObjects/GamePlane.h"
#include "Graphics/Camera3D.h"
#include "Physics/RigidBody.h"
#include "Physics/ShapeBox.h"
#include "Physics/InertiaCalculator.h"
#include "Physics/SimulationWorld.h"

using namespace Game;

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

#ifdef USE_DEBUG_CONSOLE

	/// If true then we draw contacts for the box

	bool GameBox::s_draw_contacts = false;

	/// If true then we draw the result of a raycast against the box

	bool GameBox::s_draw_raycast = true;

#endif

//=====================================================================================================================
/// @brief		Creates the plane at the specified height
/// @param		position - the position of the box
/// @param		size - the size of the half extents of the box
//=====================================================================================================================

GameBox::GameBox( const Vec3f & position , const Vec3f & size )
{
	// Create our box shape

	Shape * shape = new ShapeBox(size,position);

	// Make our rigid body:

	m_body = new RigidBody
	(
		shape														,	// shape
		true														,	// moveable ?
		50															,	// mass
		InertiaCalculator::CuboidTensor(50,size.x,size.y,size.z)	,	// inertia tensor
		0.25f														,	// coefficient of restitution
		100.0f														,	// contact point velocity under which restitution is scaled back
		0.50f														,	// static friction
		0.30f														,	// dynamic friction
		0.95f														,	// linear dampening
		0.95f															// angular dampening
	);

	// Add to the world:
	
	GameCore::GetSingleton()->GetSimulationWorld()->Add(m_body);

	// Create our graphics resources
	
	m_ps		= GameCore::GetSingleton()->GetPixelShaderManager()->GetPixelShader("Shaders\\Textured.psh.bin");
	m_vs		= GameCore::GetSingleton()->GetVertexShaderManager()->GetVertexShader("Shaders\\Textured.vsh.bin");
	m_texture	= GameCore::GetSingleton()->GetTextureManager()->GetTexture("Textures\\Box.dds");

	// Load the basic pixel shader for line drawing

	m_basic_ps = GameCore::GetSingleton()->GetPixelShaderManager()->GetPixelShader("Shaders\\basic.psh.bin");
	
	// The transform shader uniform
	
	if ( m_vs.IsValid() ) m_u_transform = m_vs.GetUniform("Transform");
	
	// Make the vertex format:
	
	VertexFormat::ElementUsage usages[] = 
	{
		VertexFormat::VF_USAGE_POSITION ,
		VertexFormat::VF_USAGE_TEXCOORD ,
		VertexFormat::VF_USAGE_COLOR			
	};
	
	VertexFormat::ElementType types[] = 
	{
		VertexFormat::VF_TYPE_FLOAT3 ,
		VertexFormat::VF_TYPE_FLOAT2 ,
		VertexFormat::VF_TYPE_FLOAT4			
	};
	
	m_vf = VertexFormat(GameCore::GetSingleton()->GetDisplay(),3,usages,types);
	
	// See if everything is ok:	
	
	if ( m_vs.IsValid() && m_ps.IsValid() && m_texture.IsValid() && m_vf.IsValid() && m_u_transform.IsValid() )
	{
		m_valid_resources = true;
	}
	else
	{
		m_valid_resources = false;
	}

	// Debug only

	#ifdef USE_DEBUG_CONSOLE
	
		DebugConsole::AddCommand( "P_DrawBoxContacts"	, ConCmd_P_DrawBoxContacts	);
		DebugConsole::AddCommand( "P_DrawBoxRaycast"	, ConCmd_P_DrawBoxRaycast	);

	#endif
}

//=====================================================================================================================
/// @brief		Destroys the box
//=====================================================================================================================

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

	delete m_body;

	// Debug only

	#ifdef USE_DEBUG_CONSOLE

		DebugConsole::RemoveCommand( "P_DrawBoxContacts"	);
		DebugConsole::RemoveCommand( "P_DrawBoxRaycast"		);

	#endif
}

//=====================================================================================================================
/// @brief		Renders the box
/// @param		c - camera to draw from
/// @pre		Given camera cannot be null
//=====================================================================================================================

void GameBox::OnDraw( Camera3D * c )
{
	// Make sure ok
	
	ENSURE( c , "Given camera cannot be null" )

	// Only if we have valid graphics resources
	
	if ( m_valid_resources )
	{
		// Apply shaders and vertex format
		
		m_vf.Apply();
		m_vs.Apply();
		m_ps.Apply();
		
		// Apply texture
		
		m_texture.Apply(0);
		
		// Set the shader transform
		
		m_u_transform.SetMatrix
		( 
			m_body->GetWorldOrientation().ToMatrix4() * Matrix4f::Translation(m_body->GetWorldPosition()) * c->GetViewProjectionMatrix() 
		);
		
		// Makeup the vertices for drawing:
		
		struct Vertex
		{
			Vec3f m_pos;
			Vec2f m_tex;
			Vec4f m_col;
		
		} vertices[6*6];

		// Get the size of our box

		const Vec3f & size = ((ShapeBox&)(m_body->GetShape())).GetSize();
		
		// Fill in the vertices:
		
			// Top face:
			
			vertices[0].m_pos = Vec3f( - size.x , + size.y , - size.z );
			vertices[1].m_pos = Vec3f( + size.x , + size.y , - size.z );
			vertices[2].m_pos = Vec3f( + size.x , + size.y , + size.z );
			vertices[3].m_pos = Vec3f( + size.x , + size.y , + size.z );
			vertices[4].m_pos = Vec3f( - size.x , + size.y , + size.z );
			vertices[5].m_pos = Vec3f( - size.x , + size.y , - size.z );

			vertices[0].m_tex = Vec2f( 0 , 0 );
			vertices[1].m_tex = Vec2f( 1 , 0 );
			vertices[2].m_tex = Vec2f( 1 , 1 );
			vertices[3].m_tex = Vec2f( 1 , 1 );
			vertices[4].m_tex = Vec2f( 0 , 1 );
			vertices[5].m_tex = Vec2f( 0 , 0 );
			
			// Bottom face:
			
			vertices[6].m_pos = Vec3f( - size.x , - size.y , - size.z );
			vertices[7].m_pos = Vec3f( + size.x , - size.y , - size.z );
			vertices[8].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[9].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[10].m_pos = Vec3f( - size.x , - size.y , + size.z );
			vertices[11].m_pos = Vec3f( - size.x , - size.y , - size.z );

			vertices[6].m_tex = Vec2f( 0 , 0 );
			vertices[7].m_tex = Vec2f( 1 , 0 );
			vertices[8].m_tex = Vec2f( 1 , 1 );
			vertices[9].m_tex = Vec2f( 1 , 1 );
			vertices[10].m_tex = Vec2f( 0 , 1 );
			vertices[11].m_tex = Vec2f( 0 , 0 );
			
			// Left face:
			
			vertices[12].m_pos = Vec3f( - size.x , + size.y , - size.z );
			vertices[13].m_pos = Vec3f( - size.x , + size.y , + size.z );
			vertices[14].m_pos = Vec3f( - size.x , - size.y , + size.z );
			vertices[15].m_pos = Vec3f( - size.x , - size.y , + size.z );
			vertices[16].m_pos = Vec3f( - size.x , - size.y , - size.z );
			vertices[17].m_pos = Vec3f( - size.x , + size.y , - size.z );

			vertices[12].m_tex = Vec2f( 0 , 0 );
			vertices[13].m_tex = Vec2f( 1 , 0 );
			vertices[14].m_tex = Vec2f( 1 , 1 );
			vertices[15].m_tex = Vec2f( 1 , 1 );
			vertices[16].m_tex = Vec2f( 0 , 1 );
			vertices[17].m_tex = Vec2f( 0 , 0 );
			
			// Right face:
			
			vertices[18].m_pos = Vec3f( + size.x , + size.y , - size.z );
			vertices[19].m_pos = Vec3f( + size.x , + size.y , + size.z );
			vertices[20].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[21].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[22].m_pos = Vec3f( + size.x , - size.y , - size.z );
			vertices[23].m_pos = Vec3f( + size.x , + size.y , - size.z );

			vertices[18].m_tex = Vec2f( 0 , 0 );
			vertices[19].m_tex = Vec2f( 1 , 0 );
			vertices[20].m_tex = Vec2f( 1 , 1 );
			vertices[21].m_tex = Vec2f( 1 , 1 );
			vertices[22].m_tex = Vec2f( 0 , 1 );
			vertices[23].m_tex = Vec2f( 0 , 0 );
			
			// Back face:
			
			vertices[24].m_pos = Vec3f( - size.x , + size.y , + size.z );
			vertices[25].m_pos = Vec3f( + size.x , + size.y , + size.z );
			vertices[26].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[27].m_pos = Vec3f( + size.x , - size.y , + size.z );
			vertices[28].m_pos = Vec3f( - size.x , - size.y , + size.z );
			vertices[29].m_pos = Vec3f( - size.x , + size.y , + size.z );

			vertices[24].m_tex = Vec2f( 0 , 0 );
			vertices[25].m_tex = Vec2f( 1 , 0 );
			vertices[26].m_tex = Vec2f( 1 , 1 );
			vertices[27].m_tex = Vec2f( 1 , 1 );
			vertices[28].m_tex = Vec2f( 0 , 1 );
			vertices[29].m_tex = Vec2f( 0 , 0 );
			
			// Front face:
			
			vertices[30].m_pos = Vec3f( - size.x , + size.y , - size.z );
			vertices[31].m_pos = Vec3f( + size.x , + size.y , - size.z );
			vertices[32].m_pos = Vec3f( + size.x , - size.y , - size.z );
			vertices[33].m_pos = Vec3f( + size.x , - size.y , - size.z );
			vertices[34].m_pos = Vec3f( - size.x , - size.y , - size.z );
			vertices[35].m_pos = Vec3f( - size.x , + size.y , - size.z );

			vertices[30].m_tex = Vec2f( 0 , 0 );
			vertices[31].m_tex = Vec2f( 1 , 0 );
			vertices[32].m_tex = Vec2f( 1 , 1 );
			vertices[33].m_tex = Vec2f( 1 , 1 );
			vertices[34].m_tex = Vec2f( 0 , 1 );
			vertices[35].m_tex = Vec2f( 0 , 0 );											
					
		// Set the colors
		
		for ( unsigned i = 0 ; i < 36 ; ++i ) 
		{
			vertices[i].m_col = Vec4f::One();
		}
		
		// Draw:
		
		GameCore::GetSingleton()->GetDisplay()->GetDevice()->DrawPrimitiveUP
		(
			D3DPT_TRIANGLELIST		,
			12						,
			vertices				,
			m_vf.GetVertexSize()
		);
		
		#ifdef USE_DEBUG_CONSOLE

			// Draw contacts if enabled:

			if ( s_draw_contacts ) DrawContacts(c);

			// Draw box raycast if enabled:

			if ( s_draw_raycast ) DrawRaycast(c);
			
		#else
		
			// In release builds draw the box raycast anyway:
			
			DrawRaycast(c);
		
		#endif
	}
}

//=====================================================================================================================
/// @brief		Updates the box and does the physics
/// @param		time_delta - the amount of time elapsed between this frame and the last
/// @todo		Remove temporary code
//=====================================================================================================================

#pragma warning(disable:4100)

void GameBox::OnUpdate( float time_delta )
{
	// Check for keyboard input

	CheckKeyboardInput();
}

#pragma warning(default:4100)

//=====================================================================================================================
/// @brief		Checks if we are to apply a force from keyboard input
//=====================================================================================================================

void GameBox::CheckKeyboardInput()
{	
	const float IMPULSE = 200.0f;

	// Lift the boxes up if space is pressed:

	if ( Keyboard::IsKeyPressed(Keyboard::KB_SPACE ) ) m_body->ApplyWorldImpulse( Vec3f(0,+IMPULSE,0) );
}

//=====================================================================================================================
/// @brief		This debug function draws a raycast against the rigid body from the camera position
/// @param		c - camera we are drawing from
//=====================================================================================================================

void GameBox::DrawRaycast( Camera3D * c )
{
	// Only if these are valid:

	if ( ( m_valid_resources && m_basic_ps.IsValid() ) == false ) return;

	// Get the orientation of the camera:

	const Matrix4f & orientation = c->GetOrientation();

	// Get the forward vector for the camera:

	Vec3f fwd = -orientation.GetCol(2).ToVec3();

	// Cast a ray against the box

	Ray ray(c->GetPosition(),fwd);
	
	// Do it:

	RaycastResult result = m_body->GetShape().Raycast(ray);

	// See if there was a hit:

	if ( result.m_hit )
	{
		// RNG

		Random random;

		// Makeup a couple of random vectors going from the hit point and draw them

		struct Vertex
		{
			Vec3f m_pos;
			Vec2f m_tex;
			Vec4f m_col;
		
		} vertices[2*12];

		// Fill in the array:

		for ( unsigned i = 0 ; i < 12 ; i++ )
		{
			vertices[ i * 2 + 0 ].m_pos = result.m_hit_point;
			vertices[ i * 2 + 1 ].m_pos = result.m_hit_point;

			vertices[ i * 2 + 1 ].m_pos.x += ( random.NextFloat() - 0.5f ) * 1.00f;
			vertices[ i * 2 + 1 ].m_pos.y += ( random.NextFloat() - 0.5f ) * 1.00f;
			vertices[ i * 2 + 1 ].m_pos.z += ( random.NextFloat() - 0.5f ) * 1.00f;
		}

		// Apply shaders etc.

		m_vf.Apply();
		m_basic_ps.Apply();
		m_vs.Apply();

		// Get the D3D device:

		IDirect3DDevice9 * device = GameCore::GetSingleton()->GetDisplay()->GetDevice();

		// Set the shader transform
		
		m_u_transform.SetMatrix( c->GetViewProjectionMatrix() );

		// Draw the vertices:

		device->DrawPrimitiveUP
		(
			D3DPT_LINELIST			,
			12						,
			vertices				,
			m_vf.GetVertexSize()
		);

		// Draw the normal:

		/* vertices[ 1 ].m_pos = result.m_hit_point + result.m_hit_normal * 4.0f;

		// Draw:

		device->DrawPrimitiveUP
		(
			D3DPT_LINELIST			,
			1						,
			vertices				,
			m_vf.GetVertexSize()

		); */
	}
}


#ifdef USE_DEBUG_CONSOLE

//=====================================================================================================================
/// @brief		This debug function draws contacts for the rigid body
/// @param		c - camera the scene is being drawn from
//=====================================================================================================================

void GameBox::DrawContacts( Camera3D * c )
{
	// Only if these are valid:

	if ( ( m_valid_resources && m_basic_ps.IsValid() ) == false ) return;

	// Get the plane:

	GamePlane * plane = GetManager()->FindByType<GamePlane>("GamePlane");

	// See if there:

	if ( plane )
	{
		// Generate a list of contacts with the plane:

		std::vector<Contact> contacts;
	
		m_body->GetShape().GenerateContacts(plane->GetBody()->GetShape(),contacts);

		// See if any contacts:

		if ( contacts.size() > 0 )
		{
			// Apply shaders etc.

			m_vf.Apply();
			m_basic_ps.Apply();
			m_vs.Apply();

			// Get the D3D device:

			IDirect3DDevice9 * device = GameCore::GetSingleton()->GetDisplay()->GetDevice();

			// Set the shader transform
			
			m_u_transform.SetMatrix( c->GetViewProjectionMatrix() );

			// Draw all of the contacts:

			for ( unsigned i = 0 ; i < contacts.size() ; ++i )
			{
				// Get the contact orientation matrix

				Matrix3f o = contacts[i].GetContactSpaceOrientationMatrix();

				// Makeup three lines representing the orientation matrix

				struct Vertex
				{
					Vec3f m_pos;
					Vec2f m_tex;
					Vec4f m_col;
				
				} vertices[2*3];

				// Fill in:

				vertices[0].m_pos = contacts[i].m_position;
				vertices[1].m_pos = contacts[i].m_position + o.GetCol(0) * 0.5f;
				vertices[2].m_pos = contacts[i].m_position;
				vertices[3].m_pos = contacts[i].m_position + o.GetCol(1) * 0.5f;
				vertices[4].m_pos = contacts[i].m_position;
				vertices[5].m_pos = contacts[i].m_position + o.GetCol(2) * 0.5f;

				// Draw:

				device->DrawPrimitiveUP(D3DPT_LINELIST,3,vertices,m_vf.GetVertexSize());
			}
		}
	}
}

//=====================================================================================================================
/// @brief		Console command: enables drawing of contacts for the rigid body
/// @param		arg - argument
/// @param		arg_given - if an argument was given
//=====================================================================================================================

bool GameBox::ConCmd_P_DrawBoxContacts( bool arg  , bool arg_given )
{
	if ( arg_given ) s_draw_contacts = arg; return s_draw_contacts;
}

//=====================================================================================================================
/// @brief		Console command: enables drawing of a raycast against the box
/// @param		arg - argument
/// @param		arg_given - if an argument was given
//=====================================================================================================================

bool GameBox::ConCmd_P_DrawBoxRaycast( bool arg  , bool arg_given )
{
	if ( arg_given ) s_draw_raycast = arg; return s_draw_raycast;
}

#endif		// #ifdef USE_DEBUG_CONSOLE
