//////////////////////////////////////////////////////////////////////////////////////////
//
//  FILE        :  Tools_WaterProcessor.cpp
//
//  CREATED     :  Richard J. Fine  (28.4.2005)
//
//  Copyright (C) 2005 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////////////////


#include "Core/Asura.h"

// This file's header.
#include "MeshProcessing/Tools_WaterProcessor.h"

#ifdef ASURA_USE_WATER

#include "AI/Asura_AI_Navigation.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_PatchSubdivision.h"
#include "Rendering/Asura_Surface.h"
#include "Rendering/Asura_Water.h"
#include "SpecialFX/Asura_Weather_System.h"

#include "MeshEdit/Tools_Mode_MeshEdit.h"
#include "MeshProcessing/Tools_SubdividedMesh.h"
#include "MeshProcessing/Asura_TriStripper.h"

#include "Load&Save/LevelExport/Tools_Level_Export.h"

List<Asura_Mesh*> Tools_WaterProcessor::s_xMeshes;
List<Tools_WaterProcessor_Strip*> Tools_WaterProcessor::s_xStrips;


bool Tools_WaterProcessor::Initialise()
{
	// grab all water in each mesh
	for (int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
	{
		Tools_Level_Export::UpdateStatus(EXPORT_STAGE_WATER_COLLECTING_MESHES, "Water: Collecting meshes:", (i * 100) / Asura_Module_System::GetNumberOfModules());
		Asura_Mesh *pxMesh = Asura_Module_System::GetMeshForModule(i);
		if(!pxMesh->PolygonFlagDataValid) continue;
		if(!pxMesh->VertexDataValid) continue;
		if(!pxMesh->TextureDataValid) continue;

		// create a temp mesh for the water extracted from this module
		char szTempMeshName[30];
		Asura_PrintfToBfr(szTempMeshName, "_tempWater%i.%i", 30, true, i, i);
		Asura_Mesh* pxThisModuleWater = Asura_Mesh_System::GetMesh(szTempMeshName, true);

		// extract water polygons
		Tools_Mode_MeshEdit::CopyMesh(pxMesh, pxThisModuleWater, ExtractWaterPolys, Tools_Mode_MeshEdit::CMNoPolyFlags 
																				  | Tools_Mode_MeshEdit::CMNoPolyGuids 
																				  | Tools_Mode_MeshEdit::CMNoPolyLights);

		if(!pxThisModuleWater->NumberOfPolygons)
		{
			Asura_Mesh_System::DeleteMesh(pxThisModuleWater);
			continue;
		}

		s_xMeshes.add_entry(pxThisModuleWater);
	}
	return (s_xMeshes.size() != 0);
}

void Tools_WaterProcessor::Shutdown()
{
	while(s_xMeshes.size())
	{
		Asura_Mesh_System::DeleteMesh(s_xMeshes.last_entry());
		s_xMeshes.delete_last_entry();
	}
	while(s_xStrips.size())
	{
		delete s_xStrips.last_entry();
		s_xStrips.delete_last_entry();
	}
}

int Tools_WaterProcessor::GetNumMeshes()
{
	return s_xMeshes.size();
}

Asura_Mesh* Tools_WaterProcessor::GetMesh(int iMeshIndex)
{
	Asura_Assert(iMeshIndex >= 0 && iMeshIndex < s_xMeshes.size(), "Index out of range");
	return s_xMeshes[iMeshIndex];
}

Tools_WaterProcessor_Strip* Tools_WaterProcessor::GetStrip(int iMeshIndex)
{
	Asura_Assert(iMeshIndex >= 0 && iMeshIndex < s_xMeshes.size(), "Index out of range");
	return s_xStrips[iMeshIndex];
}

void Tools_WaterProcessor::Subdivide()
{
	if(Asura_AI_Navigation_System::GetNumberOfWaypoints() == 0)
	{
		Asura_Assert(false, "Can't subdivide water - no waypoints");
		return;
	}
	Asura_PatchSubdivision::SubdivideNearWaypoints();
	Asura_PatchSubdivision::SetSubdivideFlat(true);
	Asura_PatchSubdivision::SetAvoidTriangleSlivers(false);
	Asura_PatchSubdivision::SetPointSearchInitialDistance(1000.f);
	Asura_PatchSubdivision::SetPointSearchMinimumDistance(0.5f);
	Asura_PatchSubdivision::SetFlatSubdivideSize(0.0125f);
	Asura_PatchSubdivision::SetCurveSubdivideSize(10000.f);
	Asura_PatchSubdivision::SetDistancePower(1.5f);

	int iMesh = 0;

	for(CLIF<Asura_Mesh*> xIt(&s_xMeshes); !xIt.done(); xIt.next(), ++iMesh)
	{
		Asura_Mesh* pxWaterMesh = xIt();

		Tools_Level_Export::UpdateStatus(EXPORT_STAGE_WATER_SUBDIVIDING_MESHES, "Water: Subdividing meshes:", (iMesh * 100) / s_xMeshes.size());

		Tools_Mode_MeshEdit::WeldVertices(pxWaterMesh);

		// get the module number from the mesh name
		Asura_String sNumber;
		pxWaterMesh->Name.SplitFilename(NULL, NULL, &sNumber);
		int iModuleNumber = 0;
		for(int i = 0; i < sNumber.GetLength(); ++i) { iModuleNumber *= 10; iModuleNumber += (sNumber[i] - '0'); }

		Asura_Vector_3 xModulePosition;
		Asura_Module_System::GetPositionOfModule(iModuleNumber, xModulePosition);
		Asura_PatchSubdivision::FindPointsNearModule(pxWaterMesh, xModulePosition);

		// subdivision's going to require smoothing group data
		// generate some
		if(!pxWaterMesh->SmoothingGroupDataValid)
		{
			pxWaterMesh->SmoothingGroupDataPtr = new int[pxWaterMesh->NumberOfPolygons];
			// set the top bit on each poly to indicate 'not curved'
			for(int u = 0; u < pxWaterMesh->NumberOfPolygons; ++u)
			{
				//pxWaterMesh->SmoothingGroupDataPtr[u] = 0x80000000;
				pxWaterMesh->SmoothingGroupDataPtr[u] = 0x00000000;
			}
			pxWaterMesh->SmoothingGroupDataValid = true;
		}

		Tools_SubdividedMesh* pxSubdividedMesh = new Tools_SubdividedMesh(pxWaterMesh, xModulePosition);
		pxSubdividedMesh->ConvertToMesh(pxWaterMesh);
		delete pxSubdividedMesh;

		Asura_Surface_Mesh_System::GetSurfaceMesh(pxWaterMesh->Index)->Invalidate();
		Tools_Mode_MeshEdit::WeldVertices(pxWaterMesh);

		// merge polys back into quads
		List<int> l;
		for(int u = 0; u < pxWaterMesh->NumberOfPolygons; ++u)
		{
			l.add_entry(u);	
		}

		// merge requires smoothing groups... again...
		if(!pxWaterMesh->SmoothingGroupDataValid)
		{
			pxWaterMesh->SmoothingGroupDataPtr = new int[pxWaterMesh->NumberOfPolygons];
			// set the top bit on each poly to indicate 'not curved'
			for(int u = 0; u < pxWaterMesh->NumberOfPolygons; ++u)
			{
				//pxWaterMesh->SmoothingGroupDataPtr[u] = 0x80000000;
				pxWaterMesh->SmoothingGroupDataPtr[u] = 0x00000000;
			}
			pxWaterMesh->SmoothingGroupDataValid = true;
		}

		int* piPolyConv;
		Tools_Mode_MeshEdit::MergePolygons(pxWaterMesh, l, &piPolyConv);
		delete[] piPolyConv;
	}

	Asura_PatchSubdivision::SetPointSearchInitialDistance(100.f);
	Asura_PatchSubdivision::SetPointSearchMinimumDistance(5.f);
	Asura_PatchSubdivision::SetDistancePower(1.f);
	Asura_PatchSubdivision::SetAvoidTriangleSlivers(true);
}

void Tools_WaterProcessor::SplitQuads()
{
	//get the sun direction to help with deciding how to split quads
	const Asura_Water_System::Env_Water_Controls& xControl = Asura_Water_System::GetEnvController();
	Asura_Vector_3 xSunDir;
	if(xControl.m_bUseLevelSun)
	{
		xSunDir = Asura_Weather_System::GetSunPos();
	}
	else
	{
		xSunDir.x = xControl.m_afSunDir[0];
		xSunDir.z = xControl.m_afSunDir[2];
	}
	xSunDir.y = 0.0f;

	Asura_Mesh* pxQuadMesh = Asura_Mesh_System::GetMesh("tempWaterQuadMesh", true);

	int iMesh = 0;

	for(CLIF<Asura_Mesh*> xIt(&s_xMeshes); !xIt.done(); xIt.next(), ++iMesh)
	{
		Asura_Mesh* pxWaterMesh = xIt();

		Tools_Level_Export::UpdateStatus(EXPORT_STAGE_WATER_SPLITTING_QUADS, "Water: Splitting quads:", (iMesh * 100) / s_xMeshes.size());

		// Extract any quads from the water mesh
		Tools_Mode_MeshEdit::CopyMesh(pxWaterMesh, pxQuadMesh, ExtractQuads, Tools_Mode_MeshEdit::CMNoSmoothingGroups);

		if(pxQuadMesh->NumberOfPolygons)
		{
			Asura_Mesh_Polygon* pxNewPolyData = new Asura_Mesh_Polygon[pxQuadMesh->NumberOfPolygons * 2];
			Asura_Mesh_TextureInfo* pxNewTexData = new Asura_Mesh_TextureInfo[pxQuadMesh->NumberOfPolygons * 2];
			Asura_Mesh_Prelighting* pxNewPrelightingData = new Asura_Mesh_Prelighting[pxQuadMesh->NumberOfPolygons * 2];

			for(int u = 0; u < pxQuadMesh->NumberOfPolygons; ++u)
			{
				Asura_Mesh_Polygon* pxPoly = &pxQuadMesh->PolygonDataPtr[u];

				//need to pick the split direction ,so that its as close to perpendicular
				//to the sun as possible
				Asura_Vector_3 xDir1 = pxQuadMesh->VertexDataPtr[pxPoly->VertexID[2]].Position
									 - pxQuadMesh->VertexDataPtr[pxPoly->VertexID[0]].Position;
	
				Asura_Vector_3 xDir2 = pxQuadMesh->VertexDataPtr[pxPoly->VertexID[3]].Position
									 - pxQuadMesh->VertexDataPtr[pxPoly->VertexID[1]].Position;
	
				float fDot1 = Asura_Maths::Abs(xSunDir*xDir1);
				float fDot2 = Asura_Maths::Abs(xSunDir*xDir2);

				int iIndices[6];
				if( fDot1 < fDot2 )
				{
					// split from 0 to 2
					iIndices[0] = 0;
					iIndices[1] = 1;
					iIndices[2] = 2;
					iIndices[3] = 0;
					iIndices[4] = 2;
					iIndices[5] = 3;
				}
				else
				{
					// split from 1 to 3
					iIndices[0] = 0;
					iIndices[1] = 1;
					iIndices[2] = 3;
					iIndices[3] = 1;
					iIndices[4] = 2;
					iIndices[5] = 3;
				}
				
				// write out the new triangles

				pxNewPolyData[u*2].VertexID[0] = pxPoly->VertexID[iIndices[0]];
				pxNewPolyData[u*2].VertexID[1] = pxPoly->VertexID[iIndices[1]];
				pxNewPolyData[u*2].VertexID[2] = pxPoly->VertexID[iIndices[2]];
				pxNewPolyData[u*2].VertexID[3] = 0xFFFF;

				pxNewPolyData[u*2+1].VertexID[0] = pxPoly->VertexID[iIndices[3]];
				pxNewPolyData[u*2+1].VertexID[1] = pxPoly->VertexID[iIndices[4]];
				pxNewPolyData[u*2+1].VertexID[2] = pxPoly->VertexID[iIndices[5]];
				pxNewPolyData[u*2+1].VertexID[3] = 0xFFFF;
				
				// texturing
				pxNewTexData[u*2].m_iMaterialID = pxQuadMesh->TextureDataPtr[u].m_iMaterialID;
				pxNewTexData[u*2].m_afAlpha[0] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[0]];
				pxNewTexData[u*2].m_afAlpha[1] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[1]];
				pxNewTexData[u*2].m_afAlpha[2] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[2]];
				for(u_int uTex = 0; uTex < ASURA_MATERIAL_MAXTEXTURES; ++uTex)
				{
					pxNewTexData[u*2].U[uTex][0] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[0]];
					pxNewTexData[u*2].U[uTex][1] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[1]];
					pxNewTexData[u*2].U[uTex][2] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[2]];

					pxNewTexData[u*2].V[uTex][0] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[0]];
					pxNewTexData[u*2].V[uTex][1] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[1]];
					pxNewTexData[u*2].V[uTex][2] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[2]];
				}

				pxNewTexData[u*2+1].m_iMaterialID = pxQuadMesh->TextureDataPtr[u].m_iMaterialID;
				pxNewTexData[u*2+1].m_afAlpha[0] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[3]];
				pxNewTexData[u*2+1].m_afAlpha[1] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[4]];
				pxNewTexData[u*2+1].m_afAlpha[2] = pxQuadMesh->TextureDataPtr[u].m_afAlpha[iIndices[5]];
				for(u_int uTex = 0; uTex < ASURA_MATERIAL_MAXTEXTURES; ++uTex)
				{
					pxNewTexData[u*2+1].U[uTex][0] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[3]];
					pxNewTexData[u*2+1].U[uTex][1] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[4]];
					pxNewTexData[u*2+1].U[uTex][2] = pxQuadMesh->TextureDataPtr[u].U[uTex][iIndices[5]];

					pxNewTexData[u*2+1].V[uTex][0] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[3]];
					pxNewTexData[u*2+1].V[uTex][1] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[4]];
					pxNewTexData[u*2+1].V[uTex][2] = pxQuadMesh->TextureDataPtr[u].V[uTex][iIndices[5]];
				}

				// prelighting
				pxNewPrelightingData[u*2].Colour[0] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[0]];
				pxNewPrelightingData[u*2].Colour[1] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[1]];
				pxNewPrelightingData[u*2].Colour[2] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[2]];

				pxNewPrelightingData[u*2+1].Colour[0] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[3]];
				pxNewPrelightingData[u*2+1].Colour[1] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[4]];
				pxNewPrelightingData[u*2+1].Colour[2] = pxQuadMesh->PrelightingDataPtr[u].Colour[iIndices[5]];
			}

			delete[] pxQuadMesh->PolygonDataPtr;			pxQuadMesh->PolygonDataPtr = pxNewPolyData;
			delete[] pxQuadMesh->TextureDataPtr;			pxQuadMesh->TextureDataPtr = pxNewTexData;
			delete[] pxQuadMesh->PrelightingDataPtr;		pxQuadMesh->PrelightingDataPtr = pxNewPrelightingData;
			pxQuadMesh->NumberOfPolygons *= 2;
			Asura_Assert(pxQuadMesh->NumberOfPolygons == pxQuadMesh->NumberOfTriangles, "Didn't split all quads");

			// delete all but quads from the original watermesh
			Tools_Mode_MeshEdit::CopyMesh(pxWaterMesh, pxWaterMesh, ExtractTriangles, Tools_Mode_MeshEdit::CMNoSmoothingGroups
																					| Tools_Mode_MeshEdit::CMNoPolyFlags 
																					| Tools_Mode_MeshEdit::CMNoPolyGuids 
																					| Tools_Mode_MeshEdit::CMNoPolyLights );

			// merge these new triangles back into the watermesh
			Tools_Mode_MeshEdit::MergeMeshes(pxWaterMesh, pxWaterMesh, pxQuadMesh, Tools_Mode_MeshEdit::CMNoSmoothingGroups
																					| Tools_Mode_MeshEdit::CMNoPolyFlags 
																					| Tools_Mode_MeshEdit::CMNoPolyGuids 
																					| Tools_Mode_MeshEdit::CMNoPolyLights );

			Tools_Mode_MeshEdit::WeldVertices(pxWaterMesh);
		}
	}
}

void Tools_WaterProcessor::Stripify()
{
	int iMesh = 0;

	for(CLIF<Asura_Mesh*> xIt(&s_xMeshes); !xIt.done(); xIt.next(), ++iMesh)
	{
		Asura_Mesh* pxMesh = xIt();

		Tools_Level_Export::UpdateStatus(EXPORT_STAGE_WATER_STRIPIFYING_MESHES, "Water: Stripifying meshes:", (iMesh * 100) / s_xMeshes.size());

		Tools_Mode_MeshEdit::WeldVertices(pxMesh);

		// Build our initial index and vertex lists
		u_short* pusListIndices = new u_short[pxMesh->NumberOfPolygons * 3];
		Tools_WaterProcessor_Vertex* pxUnoptimizedVertices = new Tools_WaterProcessor_Vertex[pxMesh->NumberOfVertices];
		for(int iPoly = 0; iPoly < pxMesh->NumberOfPolygons; ++iPoly)
		{
			Asura_Assert(!pxMesh->PolygonDataPtr[iPoly].IsQuad(), "Trying to stripify a quad!");
			pusListIndices[iPoly * 3 + 0] = pxMesh->PolygonDataPtr[iPoly].VertexID[0];
			pusListIndices[iPoly * 3 + 1] = pxMesh->PolygonDataPtr[iPoly].VertexID[1];
			pusListIndices[iPoly * 3 + 2] = pxMesh->PolygonDataPtr[iPoly].VertexID[2];

			pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 0]].m_xPosition = pxMesh->VertexDataPtr[pusListIndices[iPoly * 3 + 0]].Position;
			pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 1]].m_xPosition = pxMesh->VertexDataPtr[pusListIndices[iPoly * 3 + 1]].Position;
			pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 2]].m_xPosition = pxMesh->VertexDataPtr[pusListIndices[iPoly * 3 + 2]].Position;
			if(pxMesh->PrelightingDataValid)
			{
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 0]].m_xColour.FromUIntARGB(pxMesh->PrelightingDataPtr[iPoly].Colour[0]);
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 1]].m_xColour.FromUIntARGB(pxMesh->PrelightingDataPtr[iPoly].Colour[1]);
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 2]].m_xColour.FromUIntARGB(pxMesh->PrelightingDataPtr[iPoly].Colour[2]);
			}
			else
			{
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 0]].m_xColour.FromUIntARGB(0xffffffff);
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 1]].m_xColour.FromUIntARGB(0xffffffff);
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 2]].m_xColour.FromUIntARGB(0xffffffff);
			}
			if(pxMesh->TextureDataValid)
			{
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 0]].m_fU = pxMesh->TextureDataPtr[iPoly].U[0][0];
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 1]].m_fU = pxMesh->TextureDataPtr[iPoly].U[0][1];
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 2]].m_fU = pxMesh->TextureDataPtr[iPoly].U[0][2];
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 0]].m_fV = pxMesh->TextureDataPtr[iPoly].V[0][0];
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 1]].m_fV = pxMesh->TextureDataPtr[iPoly].V[0][1];
				pxUnoptimizedVertices[pusListIndices[iPoly * 3 + 2]].m_fV = pxMesh->TextureDataPtr[iPoly].V[0][2];
			}
		}

        Tools_TriStripper::Initialise();

		u_short* pusNewIndices;
        u_int uNewIndexCount = Tools_TriStripper::CreateStrips( pxMesh->NumberOfPolygons, pusListIndices, &pusNewIndices );

		u_short* pusPermutationTable;
        Tools_TriStripper::ComputeVertexPermutation( uNewIndexCount, pusNewIndices, pxMesh->NumberOfVertices, &pusPermutationTable );

        Tools_TriStripper::Shutdown();

		Tools_WaterProcessor_Strip* pxStrip = new Tools_WaterProcessor_Strip();

		pxStrip->m_pxVertices = new Tools_WaterProcessor_Vertex[pxMesh->NumberOfVertices];
		// copy across the vertices
		for(int u = 0; u < pxMesh->NumberOfVertices; ++u)
		{
			u_short usIndex = pusPermutationTable[u];
			pxStrip->m_pxVertices[u] = pxUnoptimizedVertices[usIndex];
		}

		pxStrip->m_pusIndices = pusNewIndices;
		pxStrip->m_uNumVertices = pxMesh->NumberOfVertices;
		pxStrip->m_uNumTriangles = uNewIndexCount - 2;
		
		// get the module number from the mesh name
		Asura_String sNumber;
		pxMesh->Name.SplitFilename(NULL, NULL, &sNumber);
		pxStrip->m_iModuleIndex = 0;
		for(int i = 0; i < sNumber.GetLength(); ++i) { pxStrip->m_iModuleIndex *= 10; pxStrip->m_iModuleIndex += (sNumber[i] - '0'); }

		delete[] pxUnoptimizedVertices;
		delete[] pusPermutationTable;
		delete[] pusListIndices;

		s_xStrips.add_entry(pxStrip);
	}
}

bool Tools_WaterProcessor::ExtractWaterPolys(Asura_Mesh* pxMesh, int iIndex)
{
	int iFlags = pxMesh->PolygonFlagDataPtr[iIndex].Flags;
	if(iFlags & AsuraPolygonFlag_DontExport) return false;
	return ((iFlags & AsuraPolygonFlag_Water) != 0);
}

bool Tools_WaterProcessor::ExtractQuads(Asura_Mesh* pxMesh, int iIndex)
{
	return pxMesh->PolygonDataPtr[iIndex].IsQuad();
}

bool Tools_WaterProcessor::ExtractTriangles(Asura_Mesh* pxMesh, int iIndex)
{
	return !pxMesh->PolygonDataPtr[iIndex].IsQuad();
}

#endif
