//
// Tools_Attractor_Node.cpp
//
// Created by JGP 10/05/06
//
// Copyright (C) 2006 Rebellion (Derby), All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_USE_ATTRACTORS

// This file's header
#include "Tools_Attractor_Node.h"
#include "Tools_Object_Attractor_Node.h"

// Asura headers
#include "Rendering/Asura_Render.h"
#include "Tools_Attractor.h"
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#include "Camera/Asura_Camera.h"
#include "Misc/Tools_Render.h"

///////////////////////////////////////////////////////////////////////////////////////
// Construct / Destruct
///////////////////////////////////////////////////////////////////////////////////////


Tools_Attractor_Node::Tools_Attractor_Node( class Tools_Attractor* const pxAttractor ) :
	Asura_Attractor_Node_Data( pxAttractor )
{
	m_pxPrev = m_pxNext = NULL;
	m_pxToolsNode = NULL;
	m_xRelativePosition.Zero();
}


Tools_Attractor_Node::~Tools_Attractor_Node()
{

	// if we have a previous then update it

	if ( m_pxPrev )
	{
		m_pxPrev->SetPrevAndNext( m_pxPrev->GetPrev(), GetNext() );
	}

	// if we have a next then update it

	if ( m_pxNext )
	{
		m_pxNext->SetPrevAndNext( GetPrev(), m_pxNext->GetNext() );
	}

	// if we're the first node in the attractor
	// set it to whatever the next node is
	// (this might be null and then the attractor
	// is considered dead and will be pruned)

	if ( GetAttractor()->GetFirstNode() == this )
	{
		GetAttractor()->SetFirstNode( m_pxNext );
	}

}


///////////////////////////////////////////////////////////////////////////////////////
// Get / Set
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Attractor_Node::SetPrevAndNext( Tools_Attractor_Node* const pxPrev, Tools_Attractor_Node* const pxNext ) 
{ 
	m_pxPrev = pxPrev; 
	m_pxNext = pxNext; 

	// if we're the first node on an attractor then we need to send it on if
	// we now have a last
	if ( ( m_pxPrev ) && ( GetAttractor()->GetFirstNode() == this ) )
	{
		GetAttractor()->SetFirstNode( m_pxPrev );
	}

	// Our normal may have been invalidated by the change, so update it if we need to
}

const Tools_Attractor_Node* Tools_Attractor_Node::GetPrevLooped() const
{
	if(m_pxPrev) return m_pxPrev;

	//Is first node , so get the last node in the attractor if closed
	const Tools_Attractor* pxAttractor = GetAttractor();
	if(!pxAttractor->IsClosed()) return 0;

	//there isn't a store of the last node in the attractor , so just search until we find it
	const Tools_Attractor_Node* pxNode = this;
	while(pxNode->m_pxNext) pxNode = pxNode->m_pxNext;

	return pxNode;
}

const Tools_Attractor_Node* Tools_Attractor_Node::GetNextLooped() const
{
	if(m_pxNext) return m_pxNext;

	//Is the last node , so get the first node in the attractor if closed
	const Tools_Attractor* pxAttractor = GetAttractor();
	if(!pxAttractor->IsClosed()) return 0;

	return pxAttractor->GetFirstNode();
}

void Tools_Attractor_Node::ApplyChangesForTemplateInstance(class Tools_EntityTemplateInstance* pxInstance)
{
	SetPosition(pxInstance->TransformPosition(GetPosition()));
}


void Tools_Attractor_Node::Render() const
{
	const Tools_Attractor* const pxAttractor = GetAttractor();
	Asura_Assert( pxAttractor, "Cant get env hook from node. Error" );

	// get next and last

	const Tools_Attractor_Node* pxNext = GetNext();

	if ( ( !pxNext ) && ( pxAttractor->IsClosed() ) )
	{
		pxNext = pxAttractor->GetFirstNode();
	}
		
	const Tools_Attractor_Node* pxPrev = GetPrev();

	if ( ( !pxPrev ) && ( pxAttractor->IsClosed() ) )
	{
		pxPrev = pxAttractor->GetFirstNode();
		while ( pxPrev->GetNext() )
		{
			pxPrev = pxPrev->GetNext();
		}
	}
		
	// draw line to next

	if ( pxNext )
	{
		const Asura_Vector_3 xPos0 = GetPosition();
		const Asura_Vector_3 xPos1 = pxNext->GetPosition();
		
		Asura_Bounding_Box xBoundingBox;
		xBoundingBox.SetToPoint( xPos0);
		xBoundingBox.Set( xPos1);
		
		if( true == Asura_Camera::IsAABBInFrustum( xBoundingBox))
		{
			float fRadius = pxAttractor->GetRadius();
			Asura_Vector_3 xPosDir = ( xPos1 - xPos0 );
			xPosDir.Normalise();
			Asura_Vector_3 xNormal;
			pxAttractor->GetNormalFromDirection(xNormal, xPosDir);
			
			const Asura_Vector_3 xHalfWay = ( xPos0 + xPos1 ) * 0.5f;

			Asura_Render::RenderLine(xPos0,xPos1, 0xff00ff00);
			Asura_Render::RenderLine(xHalfWay,xHalfWay + xNormal * 0.5f, 0xff00ff00);

			if (GetToolsNode()->IsGroupSelected() && Tools_Object_Attractor_Node::GetRenderVolumeMode() != TOOLS_ATTRACTOR_VOLUME_DISABLED)
			{
				Tools_Render::RenderCylinder(xPos0,xPos1, fRadius, 0xffffff00, true);
			}
			if (Tools_Object_Attractor_Node::GetRenderVolumeMode() == TOOLS_ATTRACTOR_VOLUME_SOLID)
			{
				if (GetToolsNode()->IsGroupSelected())
				{
					Tools_Render::RenderCylinder(xPos0,xPos1, fRadius * 0.05f, 0xff00ff00, false);
					Tools_Render::RenderCylinder(xHalfWay,xHalfWay + xNormal * fRadius, fRadius * 0.05f, 0xff00ff00, false);

					Tools_Render::RenderCylinder(xPos0,xPos1, fRadius, 0x2fffff00, false);
				}
			}

		}
	}

	// draw radius

	if (GetToolsNode()->IsGroupSelected() && Tools_Object_Attractor_Node::GetRenderVolumeMode() != TOOLS_ATTRACTOR_VOLUME_DISABLED)
	{
		const float fRadius = pxAttractor->GetRadius();

		if ( ( ( pxNext ) || ( pxPrev ) ) && ( fRadius > 0.0f ) )
		{
			const Asura_Vector_3 xPos0 = GetPosition();
			Asura_Bounding_Box xBoundingBox;

			xBoundingBox.SetToPoint( xPos0);
			xBoundingBox.Grow( fRadius);

			if( true == Asura_Camera::IsAABBInFrustum( xBoundingBox))
			{
				Asura_Vector_3 xPosDir;

				if ( ( pxNext ) && ( pxPrev ) )
				{
					xPosDir = ( pxNext->GetPosition() - pxPrev->GetPosition() );
				}
				else if ( pxNext )
				{
					xPosDir = ( pxNext->GetPosition() - xPos0 );
				}
				else // last only
				{
					xPosDir = ( xPos0 - pxPrev->GetPosition() );
				}

				Asura_Vector_3 xTangent = Asura_Vector_3( 0.0f, -1.0f, 0.0f ) ^ xPosDir;

				if ( xTangent.Magnitude() < 0.001f )
				{
					xTangent = Asura_Vector_3( 0.0f, 0.0f, 1.0f );
				}
				else
				{
					xTangent.Normalise();
				}

				Asura_Vector_3 xTangent2 = xTangent ^ xPosDir;
				xTangent2.Normalise();

				Asura_Vector_3 xPos = xTangent2;
 				const float fStep = 2.0f * 3.1415f / 32;
 				float fAngle = 0.0f;

 				for ( int i = 0; i < 32; i++ )
 				{
 					fAngle += fStep;
 					const Asura_Vector_3 xNextPos =  xTangent2 * Asura_Maths::Cosine( fAngle ) + xTangent * Asura_Maths::Sine( fAngle );
 
 					Asura_Render::RenderLine( xPos0 + xPos * fRadius, xPos0 + xNextPos * fRadius, 0xffffff00 );
 					xPos = xNextPos;
 				}
			}
		} else if (fRadius > 0.0f) {
			Asura_Matrix_3x3 xIdent;
			xIdent.Identity();
			Tools_Render::RenderSpheroid(GetPosition(), xIdent, Asura_Vector_3(fRadius,fRadius,fRadius), 0xffffff00, Tools_Object_Attractor_Node::GetRenderVolumeMode() == TOOLS_ATTRACTOR_VOLUME_WIREFRAME);
		}

	}

}



#endif // ASURA_USE_ATTRACTORS
