////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001-2005.
// -------------------------------------------------------------------------
//  File name:   CloudsManager.h
//  Version:     v1.00
//  Created:     15/2/2005 by Timur.
//  Compilers:   Visual Studio.NET 2003
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "CloudRenderNode.h"
#include "CloudsManager.h"


//////////////////////////////////////////////////////////////////////////
SCloudDescription::~SCloudDescription()
{
	delete m_pCloudTree;
	m_pCloudTree = 0;
	// Unregister itself from clouds manager.
	if (!filename.empty())
		Get3DEngine()->GetCloudsManager()->Unregister( this );
}

//////////////////////////////////////////////////////////////////////////
SCloudDescription* CCloudsManager::LoadCloud( const char *sFilename )
{
	string filename = PathUtil::ToUnixPath(sFilename);

	SCloudDescription *pCloud = stl::find_in_map( m_cloudsMap,filename,NULL );
	if (!pCloud)
	{
		XmlNodeRef root = GetISystem()->LoadXmlFile( filename );
		if (root)
		{
			pCloud = new SCloudDescription;
			pCloud->filename = filename;
			ParseCloudFromXml( root,pCloud );

			CloudParticles particles;
			particles.resize(pCloud->m_particles.size());
			for(int i=0; i<particles.size(); i++)
				particles[i] = &pCloud->m_particles[i];
			pCloud->m_pCloudTree = new SCloudQuadTree();
			pCloud->m_pCloudTree->Init(pCloud->m_bounds, particles);

			Register( pCloud );
		}
	}

	return pCloud;
}
	
	
//////////////////////////////////////////////////////////////////////////
void CCloudsManager::ParseCloudFromXml( XmlNodeRef root,SCloudDescription* pCloud )
{
	assert( pCloud );

	pCloud->m_bounds.min = Vec3(0,0,0);
	pCloud->m_bounds.max = Vec3(0,0,0);
	pCloud->m_pMaterial = 0;
	const char *sMtlName = root->getAttr( "Material" );
	if (sMtlName != '\0')
	{
		pCloud->m_pMaterial = GetMatMan()->LoadMaterial( sMtlName );
		
		if(!pCloud->m_pMaterial)
			CryWarning(VALIDATOR_MODULE_3DENGINE,VALIDATOR_ERROR,"Error: Failed to load cloud material"/*,sMtlName*/);
	}

	int numRows = 1;
	int numCols = 1;
	root->getAttr("TextureNumRows", numRows );
	root->getAttr("TextureNumCols", numCols );
	if (numRows < 1)
		numRows = 1;
	if (numCols < 1)
		numCols = 1;

	pCloud->m_textureRows = numRows;
	pCloud->m_textureCols = numCols;

	pCloud->m_numSprites = root->getChildCount();
	pCloud->m_particles.reserve( pCloud->m_numSprites );
	pCloud->m_particles.clear();

	float xTextureStep = 1.0f / numCols;
	float yTextureStep = 1.0f / numRows;

	Vec3 pos(0,0,0);
	int texID = 0;
	float angle = 0;
	float radius = 0;
	Vec2 uv[2];
	if (pCloud->m_numSprites > 0)
		pCloud->m_bounds.Reset();
	for(int i = 0; i < root->getChildCount(); i++)
	{
		XmlNodeRef child = root->getChild(i);
		child->getAttr("Pos", pos);
		child->getAttr("texID", texID);
		child->getAttr("Radius", radius);
		if(!child->getAttr("Angle", angle))
			angle = 0;
		
		int x = texID % numCols;
		int y = texID / numCols;

		uv[0].x = x * xTextureStep;
		uv[0].y = y * yTextureStep;
		uv[1].x = (x+1) * xTextureStep;
		uv[1].y = (y+1) * yTextureStep;

		SCloudParticle sprite( pos,radius,radius,0,0,uv );
		pCloud->m_particles.push_back( sprite );

		pCloud->m_bounds.Add( pos-Vec3(radius,radius,radius) );
		pCloud->m_bounds.Add( pos+Vec3(radius,radius,radius) );
	}

	// Offset particles so that bounding box is centered at origin.
	pCloud->m_offset = -pCloud->m_bounds.GetCenter();
	pCloud->m_bounds.min += pCloud->m_offset;
	pCloud->m_bounds.max += pCloud->m_offset;

	for (int i = 0; i < pCloud->m_particles.size(); i++)
	{
		pCloud->m_particles[i].SetPosition( pCloud->m_particles[i].GetPosition() + pCloud->m_offset );
	}
}

//////////////////////////////////////////////////////////////////////////
void CCloudsManager::Register( SCloudDescription *desc )
{
	assert(desc);
	m_cloudsMap[desc->filename] = desc;
}

//////////////////////////////////////////////////////////////////////////
void CCloudsManager::Unregister( SCloudDescription *desc )
{
	assert(desc);
	m_cloudsMap.erase( desc->filename );
}

//////////////////////////////////////////////////////////////////////////
void CCloudsManager::AddCloudRenderNode(CCloudRenderNode * pNode)
{
	m_cloudNodes.push_back(pNode);
}

//////////////////////////////////////////////////////////////////////////
void CCloudsManager::RemoveCloudRenderNode(CCloudRenderNode * pNode)
{
	int size = m_cloudNodes.size();
	for(int i=0; i<size; i++)
	{
		if(m_cloudNodes[i]==pNode)
		{
			if(i<size-1)
				m_cloudNodes[i] = m_cloudNodes[size-1];
			m_cloudNodes.resize(size-1);
			break;
		}
	}
}

//!!! WARNING
//bool Sp_IsDraw = false;
//Matrix34 Sp_Mat;
//Vec3 Sp_Pos;
//float Sp_Rad;


//////////////////////////////////////////////////////////////////////////
bool CCloudsManager::CheckIntersectClouds(const Vec3& p1, const Vec3& p2)
{
	for (std::vector<CCloudRenderNode*>::iterator it = m_cloudNodes.begin(); it != m_cloudNodes.end(); ++it)
	{
		//Sp_IsDraw = false;
		if((*it)->CheckIntersection(p1, p2))
		{
			//Sp_Mat = (*it)->m_offsetedMatrix;
			//Sp_IsDraw = true;
			return true;
		}
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
void CCloudsManager::MoveClouds()
{
  FUNCTION_PROFILER_3DENGINE;

	std::vector<CCloudRenderNode*>::iterator it(m_cloudNodes.begin());
	std::vector<CCloudRenderNode*>::iterator itEnd(m_cloudNodes.end());
	for (; it != itEnd; ++it)
	{
		(*it)->MoveCloud();
	}
}

//////////////////////////////////////////////////////////////////////////
void SCloudQuadTree::Init(const AABB & bounds, const CloudParticles & particles, int maxlevel)
{
	m_bounds = bounds;

	if(m_level >= maxlevel)
	{
		m_particles.resize(particles.size());
		memcpy(&m_particles[0], &particles[0], particles.size() * sizeof(SCloudParticle *));
		return;
	}

	CloudParticles parts;
	for(int k = 0; k<4; k++)
	{
		AABB bnds;
		parts.resize(0);

		Vec3 centr = (bounds.min+bounds.max)/2;

		if(k==0)
			bnds = AABB(bounds.min, Vec3(centr.x, centr.y, bounds.max.z));
		else if(k==1)
			bnds = AABB(Vec3(bounds.min.x, centr.y, bounds.min.z), Vec3(centr.x, bounds.max.y, bounds.max.z));
		else if(k==2)
			bnds = AABB(Vec3(centr.x, bounds.min.y, bounds.min.z), Vec3(bounds.max.x, centr.y, bounds.max.z));
		else if(k==3)
			bnds = AABB(Vec3(centr.x, centr.y, bounds.min.z), bounds.max);
		for(int i=0; i<particles.size(); i++)
		{
			SCloudParticle * pPtcl = particles[i];
			if(bnds.IsOverlapSphereBounds(pPtcl->GetPosition(), pPtcl->GetRadiusX()) || 
				 bnds.IsContainSphere(pPtcl->GetPosition(), pPtcl->GetRadiusX()))
			{
				parts.push_back(pPtcl);
			}
		}
		if(parts.size()>0)
		{
			m_pQuads[k] = new SCloudQuadTree(m_level+1);
			m_pQuads[k]->Init(bnds, parts, maxlevel);
		}
	}

}


//////////////////////////////////////////////////////////////////////////
bool SCloudQuadTree::CheckIntersection(const Vec3& p1, const Vec3& p2)
{
	Vec3 outp, outp2;
	if(Intersect::Lineseg_AABB(Lineseg(p1, p2), m_bounds, outp))
	{
		if(m_pQuads[0] && m_pQuads[0]->CheckIntersection(p1, p2))
			return true;
		if(m_pQuads[1] && m_pQuads[1]->CheckIntersection(p1, p2))
			return true;
		if(m_pQuads[2] && m_pQuads[2]->CheckIntersection(p1, p2))
			return true;
		if(m_pQuads[3] && m_pQuads[3]->CheckIntersection(p1, p2))
			return true;
		if(m_particles.size()>0)
		{
			for(int i=0; i<m_particles.size(); i++)
				//if(Intersect::Lineseg_Sphere(Lineseg(p1, p2), Sphere (m_particles[i]->GetPosition(), m_particles[i]->GetRadiusX()*(2.0f/3)), outp, outp2))
				if(Intersect::Lineseg_Sphere(Lineseg(p1, p2), Sphere (m_particles[i]->GetPosition(), m_particles[i]->GetRadiusX()), outp, outp2))
				{
					//Sp_Pos = m_particles[i]->GetPosition();
					//Sp_Rad = m_particles[i]->GetRadiusX()/2;
					return true;
				}
		}
	}
	return false;
}

/*
//!!! WARNING
extern bool Sp_IsDraw;
extern Matrix34 Sp_Mat;
extern Vec3 Sp_Pos;
extern float Sp_Rad;


void C3DEngine::Hack_GetSprite(bool & IsDraw, Matrix34 & Mat, Vec3 & Pos, float & Rad)
{
	IsDraw = Sp_IsDraw;
	Mat = Sp_Mat;
	Pos = Sp_Pos;
	Rad = Sp_Rad;
}
*/