//
// Tools_Mode_VertexPainting.cpp
//
// Created by JGP 12/06/06
//
// Copyright (C) 2006 Rebellion (Derby), All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header
#include "Tools_Mode_VertexPainting.h"

// Asura headers
#if defined( ASURA_USE_CARPET)
	#include "Carpet/Asura_Direct3D_Carpet_Game_Render.h"
	#include "Carpet/Asura_Carpet_MeshManager.h"
#endif
#include "Core/Asura_UserInput.h"
#include "Core/Asura_Timers.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Tools_ColourPicker.h"
#include "Interface/Asura_Interface.h"
#include "Light/Asura_GI_Solution.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Module.h"

#include "Rendering/Asura_Direct3D_Shaders.h"
#include "Rendering/Asura_Direct3D_StoreRenderTargets.h"
#include "Rendering/Asura_Render.h"

// Tools headers
#include "Tools_Overlays.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Vertex_Selection.h"
#include "Misc/Tools_VertexPerPoly_Selection.h"
#include "Misc/Tools_DynamicArray.h"
#include "Misc/Tools_LOS.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Misc/Tools_Render.h"
#include "Misc/Tools_Adjacency.h"
#include "Objects/Tools_Object.h"
#include "Objects/Tools_Object_System.h"



// static this pointer
Tools_Mode_VertexPainting* Tools_Mode_VertexPainting::s_pxThis = NULL;

// static variables
Asura_Vector_2 Tools_Mode_VertexPainting::s_xOverlayPosition( 3.0f, 14.0f );

#define D3DFVF_CUBEVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
struct D3DCubeVertex
{
	float X,Y,Z;
	unsigned int Diffuse;
};

static u_int s_uBufferCubeSize = 0;
static u_int s_uBufferCubeCount = 0;

static u_int s_uBufferCubeVertexSize = 0;
static u_int s_uBufferCubeVertexCount = 0;
static D3DCubeVertex* s_pxBufferCubeVertices;

static u_int s_uBufferCubeIndexSize = 0;
static u_int s_uBufferCubeIndexCount = 0;
static uint16* s_pxBufferCubeIndices;




///////////////////////////////////////////////////////////////////////////////////////
// Undo data
///////////////////////////////////////////////////////////////////////////////////////


struct VertexPaintingUndoData
{
	int m_iModuleIndex;
	int m_iPolygonIndex;
	int m_iVertexIndex;
	unsigned int m_uOldColour;
};


class VertexPaintingUndo : public FixedSizeUndo
{
public:

	VertexPaintingUndo()
		: FixedSizeUndo( 10000, sizeof( VertexPaintingUndoData ) )
	{
	}

	void ProcessUndoData( void* pData );
	void LogVertexPaint( const int iModuleIndex, const int iPolygonIndex, const int iVertexIndex, const unsigned int uOldColour );

};


void VertexPaintingUndo::ProcessUndoData( void* pData )
{
	VertexPaintingUndoData* pxUndoData = static_cast< VertexPaintingUndoData* >( pData );
	Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( pxUndoData->m_iModuleIndex );
	unsigned int& uMeshColour = pxMesh->m_pxPaintedColourDataPtr[ pxUndoData->m_iPolygonIndex ].Colour[ pxUndoData->m_iVertexIndex ];
	const unsigned int uOldColour = uMeshColour;
	uMeshColour = pxUndoData->m_uOldColour;
	pxUndoData->m_uOldColour = uOldColour;
	pxMesh->PrelightingDataValid = false;

	Tools_SaveAsura::SetMeshModified(pxMesh);
}


void VertexPaintingUndo::LogVertexPaint( const int iModuleIndex, const int iPolygonIndex, const int iVertexIndex, const unsigned int uOldColour )
{
	VertexPaintingUndoData* pxUndo = static_cast< VertexPaintingUndoData* >( AllocateNextUndoData() );
	if ( !pxUndo ) 
	{
		return;
	}

	pxUndo->m_iModuleIndex = iModuleIndex;
	pxUndo->m_iPolygonIndex = iPolygonIndex;
	pxUndo->m_iVertexIndex = iVertexIndex;
	pxUndo->m_uOldColour = uOldColour;
}


///////////////////////////////////////////////////////////////////////////////////////
// Construct and destruct
///////////////////////////////////////////////////////////////////////////////////////


Tools_Mode_VertexPainting::Tools_Mode_VertexPainting()
{
	// store this pointer
	// for static access
	s_pxThis = this;

	m_pxUndoBuffer = new VertexPaintingUndo;
	m_pxUndoBuffer->Initialise();
}


Tools_Mode_VertexPainting::~Tools_Mode_VertexPainting()
{
	m_pxUndoBuffer->Shutdown();
	delete m_pxUndoBuffer;
	s_pxThis = NULL;
}


///////////////////////////////////////////////////////////////////////////////////////
// Module Information
///////////////////////////////////////////////////////////////////////////////////////

Tools_Mode_VertexPainting::ModuleInformation::ModuleInformation()
{
	this->m_bVisible = true;
}

///////////////////////////////////////////////////////////////////////////////////////
// Tools_Mode functions
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_VertexPainting::InitialiseMode()
{

	m_bVertexPerPolySelection = false;
	m_bPaintingActive = false;
	m_fPaintingRadius = 0.5f;
	m_bPaintingOnFloor = false;
	m_bPaintingOn = false;
	
	m_bPaintMaskR = true;
	m_bPaintMaskG = true;
	m_bPaintMaskB = true;

	Tools_Object_System::EnableAllControls( false );
	Tools_Vertex_Selection::Activate();
	Tools_VertexPerPoly_Selection::Activate();

	m_fColourRed = m_fColourGreen = m_fColourBlue = 128.0f;
	m_eOp = OP_REPLACE;
	
	const u_int uModuleCount = Asura_Module_System::GetNumberOfModules();
	this->m_axModuleInformation.SetSize( uModuleCount);

	// invalidate lights (as we're turning on vertex colours if in non lit)
	InvalidateAllLighting();

	// now initialise the overlay
	InitialiseOverlay();
	
	// DLH - Allocate Vertex Cube Buffers
	const u_int uCubesVertices = 0x10000 / 8;
	const u_int uCubesIndices = 0x10000 / 36;
	const u_int uCubes = Asura_Maths::Min( uCubesVertices, uCubesIndices);
	
	s_uBufferCubeSize = uCubes;
	s_uBufferCubeCount = 0;

	s_uBufferCubeVertexSize = uCubes * 8;
	s_uBufferCubeVertexCount = 0;
	s_pxBufferCubeVertices = new D3DCubeVertex[ s_uBufferCubeVertexSize];

	s_uBufferCubeIndexSize = 36 * uCubes;
	s_uBufferCubeIndexCount = 0;
	s_pxBufferCubeIndices = new uint16[ s_uBufferCubeIndexSize];
}


void Tools_Mode_VertexPainting::ShutdownMode()
{
	// DLH - Delallocate Vertex Cube Buffers
	delete[] s_pxBufferCubeVertices;
	s_pxBufferCubeVertices = NULL;
	
	delete[] s_pxBufferCubeIndices;
	s_pxBufferCubeIndices = NULL;
	
	// DLH - Clear the memory for the Module information
	this->m_axModuleInformation.Clear();
	
	Tools_Vertex_Selection::Deactivate();
	Tools_VertexPerPoly_Selection::Deactivate();

	// invalidate lights 
	InvalidateAllLighting();

	// Update the GI samples - when exiting the vertex painting mode
	Asura_GI_Solution::Tools_InitialiseFromPhotonMappingSystem();

	// Relight the carpet data - when exiting the vertex painting mode
#if defined( ASURA_USE_CARPET)
	if( true == Asura_Direct3D_Carpet_Game_Render::HasDataSet())
	{
		Asura_Direct3D_Carpet_Game_Render::RelightEverything();
	}
	Asura_Carpet_MeshManager::UpdateLighting();
#endif

	// get rid of the overlay
	CloseOverlay();
}


void Tools_Mode_VertexPainting::QuickSave()
{
	Tools_SaveAsura::QuickSaveFile();
}



bool Tools_Mode_VertexPainting::IsModified()
{
	return Tools_SaveAsura::IsMainFileModified();
}

void Tools_Mode_VertexPainting::Update()
{

	HandleKeyboard();

	if ( m_bPaintingActive )
	{
		UpdatePainting();
		Tools_Vertex_Selection::Update( VertexSelectionUpdate_NoSelection );
		Tools_VertexPerPoly_Selection::Update( VertexPerPolySelectionUpdate_NoSelection );
	}
	else if ( m_bVertexPerPolySelection )
	{
		Tools_Vertex_Selection::CloseSelectionOverlay();
		if ( !Tools_VertexPerPoly_Selection::IsSelectionOverlayOn()) 
		{
			Tools_VertexPerPoly_Selection::ToggleSelectionOverlay();
		}
		Tools_Vertex_Selection::Update( VertexSelectionUpdate_NoSelection );
		Tools_VertexPerPoly_Selection::Update( VertexPerPolySelectionUpdate_Standard );
	}
	else
	{
		Tools_Vertex_Selection::CreateSelectionOverlay();
		if ( Tools_VertexPerPoly_Selection::IsSelectionOverlayOn()) 
		{
			Tools_VertexPerPoly_Selection::ToggleSelectionOverlay();
		}
		Tools_Vertex_Selection::Update( VertexSelectionUpdate_Standard );
		Tools_VertexPerPoly_Selection::Update( VertexPerPolySelectionUpdate_NoSelection );
	}

}


void Tools_Mode_VertexPainting::HandleKeyboard()
{
	if ( ( Asura_Overlay_System::OverlaysHaveKeyboardFocus() ) || ( Asura_UserInput::KeyPressed( ASURA_KEYID_ALT ) ) )
	{
		return;
	}

	if ( Asura_UserInput::KeyPressed( ASURA_KEYID_CTRL ) )
 	{
		if( Asura_UserInput::DebouncedKeyPressed( 'z' ) ) 
		{
			Undo();
		}
		if ( Asura_UserInput::DebouncedKeyPressed( 'y' ) )
		{
			Redo();
		}
	}
	else if ( Asura_UserInput::KeyPressed( ASURA_KEYID_SHIFT ) )
	{
	}
	else
	{
		// 'P' Toggle painting
		if( Asura_UserInput::DebouncedKeyPressed( 'p' ) )
		{
			m_bPaintingActive = !m_bPaintingActive;		
		}
		if( Asura_UserInput::DebouncedKeyPressed( ',' ) ) 
		{
			Undo();
		}
		if ( Asura_UserInput::DebouncedKeyPressed( '.' ) )
		{
			Redo();
		}

	}
}


void Tools_Mode_VertexPainting::Undo()
{
	if ( m_pxUndoBuffer->Undo() )
	{
		Tools_Timed_Message_System::AddMessage( 0xffffff, "Vertex Painting Undo" );
	}
}


void Tools_Mode_VertexPainting::Redo()
{
	if( m_pxUndoBuffer->Redo() )
	{
		Tools_Timed_Message_System::AddMessage( 0xffffff, "Vertex Painting Redo" );
	}
}


struct ModulePolyVertTime
{
	int iModule;
	int iPoly;
	int iVert;
	float fTime;
};


void Tools_Mode_VertexPainting::UpdatePainting()
{
	m_bPaintingOnFloor = Tools_LOS::FindPolyAndPositionUnderCursor( m_iPaintingPoly, m_iPaintingiModule, m_xPaintingPosition );

	if ( !m_bPaintingOnFloor )
	{
		return;
	}

	if ( Asura_Interface::UsingMenusOrOverlays() )
	{
		return;
	}

	const uint32 uMaskColour = this->GetRGBMask();
	
	if ( m_bPaintingOn )
	{
		if ( !Asura_UserInput::KeyPressed( ASURA_KEYID_MOUSEBUTTON1 ))
		{
			m_bPaintingOn = false;
		}
		else
		{

#define REPEAT_TIME 0.5f
			const float fNow = Asura_Timers::GetUncappedPhase();

			// Find verts within sphere, and attempt to update them
			for( int i = 0; i < Asura_Module_System::GetNumberOfModules(); i++ )
			{
				const Tools_Mode_VertexPainting::ModuleInformation& xModuleInfo = m_axModuleInformation[ i];
				if( false == xModuleInfo.m_bVisible)
				{
					continue;
				}
			
				if( false == Asura_Module_System::IsModuleVisible( i))
				{
					continue;
				}
			
				const Asura_Vector_3 xCentre = Asura_Module_System::GetCentreOfModule( i );
				const float fRadius = Asura_Module_System::GetRadiusOfModule( i );

				Asura_Vector_3 xOfs = m_xPaintingPosition - xCentre;
				
				// Test the sphere cursor agains the module's bounding sphere
				if ( xOfs.MagnitudeSquared() <= ( ( fRadius + m_fPaintingRadius ) * ( fRadius + m_fPaintingRadius ) ) )
				{
					// We need to test all the verts
					Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( i );
					Asura_Vector_3 xModulePosition = Asura_Module_System::GetPositionOfModule( i );
					
					int iNumPolys=pxMesh->NumberOfPolygons;
					bool bMeshModified = false;
						
					for (int j=0;j<iNumPolys;j++)
					{									
						for ( int k = 0; k < pxMesh->PolygonDataPtr[ j ].NumberOfVertices(); k++ )
						{

							const int iVertID = pxMesh->PolygonDataPtr[ j ].VertexID[ k ];
							const Asura_Vector_3 xVertexWorldPos = pxMesh->VertexDataPtr[ iVertID ].Position + xModulePosition;
							const Asura_Vector_3 xOfs = xVertexWorldPos - m_xPaintingPosition;

							if ( xOfs.MagnitudeSquared() <= m_fPaintingRadius )
							{
								bool bPaint = true;
								int iFind = 0;

								// go through all processed ones
								for ( iFind = 0; iFind < (int)m_xPaintedVerts.GetSize(); iFind++ )
								{
									// see if we can find matching module, poly, vert
									if ( ( m_xPaintedVerts[ iFind ].iModule == i ) && ( m_xPaintedVerts[ iFind ].iPoly == j ) && ( m_xPaintedVerts[ iFind ].iVert == k ) )
									{
										// if time is too close then don't paint
										if ( fNow - m_xPaintedVerts[ iFind ].fTime < REPEAT_TIME )
										{
											bPaint = false;
										}
										break;
									}
								}

								// if not paint it and store it
								if ( bPaint )
								{
									// Log undo
									m_pxUndoBuffer->LogVertexPaint( i, j, k, pxMesh->m_pxPaintedColourDataPtr[ j ].Colour[ k ] );

									const unsigned int uModulateColour = ( pxMesh->PrelightingDataValid ) ? pxMesh->PrelightingDataPtr[ j ].Colour[ k ] : 0x808080;
									ApplyTo( pxMesh->m_pxPaintedColourDataPtr[ j ].Colour[ k ], uModulateColour, uMaskColour ); 
									pxMesh->PrelightingDataValid = false;

									// if we didn't find the vert then store it
									// otherwise just update it's time
									if ( iFind == (int)m_xPaintedVerts.GetSize() )
									{
										ModulePolyVertTime xMPVT;
										xMPVT.iModule = i;
										xMPVT.iPoly = j;
										xMPVT.iVert = k;
										xMPVT.fTime = fNow;
										m_xPaintedVerts += xMPVT;
									}
									else
									{
										m_xPaintedVerts[ iFind ].fTime = fNow;
									}
									bMeshModified = true;

								}
							}
						}
					}
					if(bMeshModified) Tools_SaveAsura::SetMeshModified(pxMesh);
				}
			}
		}
	}
	else
	{
		//check for debounced press before starting painting , to prevent clicks that start on an overlay from
		//being picked up. RAR 04/09/2006
		if ( Asura_UserInput::DebouncedKeyPressed( ASURA_KEYID_MOUSEBUTTON1 ) )
		{
			m_bPaintingOn = true;
			m_xPaintedVerts.Reset();
			//Batch all changes while the mouse is held into a single undo. RAR 04/09/2006
			m_pxUndoBuffer->MarkUndoStart();

		}
	}

}


void Tools_Mode_VertexPainting::RenderWorld_PostToneMap()
{
	if ( m_bPaintingActive )
	{
		RenderPainting();
	}
	else if ( m_bVertexPerPolySelection )
	{
		Tools_VertexPerPoly_Selection::Render();
	}
	else
	{
		Tools_Vertex_Selection::Render();
	}
}


void Tools_Mode_VertexPainting::RenderPainting()
{
	this->RenderPainting_Start();
	
	for ( int iModule = 0; iModule < Asura_Module_System::GetNumberOfModules(); iModule++ )
	{
		const Tools_Mode_VertexPainting::ModuleInformation& xModuleInfo = m_axModuleInformation[ iModule];
		if( false == xModuleInfo.m_bVisible)
		{
			continue;
		}
	
		if( false == Asura_Module_System::IsModuleVisible( iModule))
		{
			continue;
		}
		
		if( false == Asura_Module_System::IsModuleInFrustum( iModule))
		{
			continue;
		}
		
		const Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( iModule );
		
		if( NULL == pxMesh)
		{
			continue;
		}
		
		const Asura_Mesh_PaintedColours* pxPaintedColourData = pxMesh->m_pxPaintedColourDataPtr;
		const Asura_Mesh_Polygon* pxPolygonData = pxMesh->PolygonDataPtr;
		if( false == pxMesh->m_bPaintedColourDataValid)
		{
			pxPaintedColourData = NULL;
			pxPolygonData = NULL;
		}
		
		if( NULL == pxPaintedColourData || NULL == pxPolygonData)
		{
			for ( int iVert = 0; iVert < pxMesh->NumberOfVertices; iVert++ )
			{
				this->RenderPainting_Stop();
				Tools_Render::RenderVertexOutline( iModule, iVert, 0x40ffffff );
				this->RenderPainting_Start();
			}
		}
		else
		{
			const u_int uPolygonCount = pxMesh->NumberOfPolygons;
			if( 0 < uPolygonCount)
			{
				Asura_Vector_3 xMeshPosition;		
				Asura_Module_System::GetPositionOfModule( iModule, xMeshPosition);
				
				for( u_int uPolygonIndex = 0; uPolygonIndex < uPolygonCount; uPolygonIndex++ )
				{
					const Asura_Mesh_Polygon& xPolygonData = pxPolygonData[ uPolygonIndex];
					const Asura_Mesh_PaintedColours& xPaintedColourData = pxPaintedColourData[ uPolygonIndex];
					
					const u_int uPolygonVertexCount = xPolygonData.NumberOfVertices();

					for( u_int uPolygonVertexIndex = 0; uPolygonVertexIndex < uPolygonVertexCount; uPolygonVertexIndex += 1)				
					{
						const u_int uVertexIndex = xPolygonData.VertexID[ uPolygonVertexIndex];
						const uint32 uPolygonVertexColour = xPaintedColourData.Colour[ uPolygonVertexIndex];
						
						const Asura_Vector_3 &xVertexPosition = pxMesh->VertexDataPtr[ uVertexIndex].Position;
						
						this->RenderPainting_Cube( xMeshPosition + xVertexPosition, uPolygonVertexColour | 0xFF000000);
					}
				}
			}			
		}
	}
	
	this->RenderPainting_Stop();

	if ( m_bPaintingOnFloor )
	{
		const int iRed = static_cast<int>( 128.0f + 0.5f * m_fColourRed );
		const int iGreen = static_cast<int>( 128.0f + 0.5f * m_fColourGreen );
		const int iBlue = static_cast<int>( 128.0f + 0.5f * m_fColourBlue );
		
		extern void Render_Sphere( const Asura_Vector_3& xCentre, float fRadius, int iColour );

		Render_Sphere( m_xPaintingPosition, m_fPaintingRadius, 0x60000000 | MakeColour( iRed, iGreen, iBlue ) );
	}

}

static void BufferCubeFlush()
{
	if( 0 == s_uBufferCubeCount)
	{
		return;
	}
	
	SCOPEDPIXEVENT((0xffffffff, L"Render Cuboid Flush"));
	
	HRESULT xResult;
	
	u_int uPrimitives = s_uBufferCubeIndexCount / 3;
	
	xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP
		(
		D3DPT_TRIANGLELIST,
		0,
		s_uBufferCubeVertexCount,
		uPrimitives,
		(void*)s_pxBufferCubeIndices,
		D3DFMT_INDEX16,
		(void*)s_pxBufferCubeVertices,
		sizeof(D3DCubeVertex)
		);
		
	ASURA_D3D_OK( xResult);
	
	s_uBufferCubeCount = 0;
	s_uBufferCubeVertexCount = 0;
	s_uBufferCubeIndexCount = 0;
	
}

void Tools_Mode_VertexPainting::RenderPainting_Start()
{
	s_uBufferCubeCount = 0;
	s_uBufferCubeVertexCount = 0;
	s_uBufferCubeIndexCount = 0;
	
	Asura_Direct3D_Shaders::RequestFixedFunctionVertexShader(D3DFVF_CUBEVERTEX);

	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

	Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_COLOUR);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	Asura_Render::CurrentStates.RequestTextureIndex(0,ASURA_TEXTURE_NONE);

	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);

}

void Tools_Mode_VertexPainting::RenderPainting_Stop()
{
	if( 0 != s_uBufferCubeCount)
	{
		BufferCubeFlush();
	}
	
	D3DXMATRIX matWorld;
	D3DXMatrixTranslation( &matWorld, 0,0,0 );
	Asura_Direct3D::D3DDevice->SetTransform( D3DTS_WORLD,      &matWorld );
	
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);

//	D3DXMATRIX matWorld;
//	Asura_Direct3D::SetupD3DMatrix(matWorld, xOrient, xPosition, true);

//	Asura_Direct3D::D3DDevice->SetTransform( D3DTS_WORLD,      &matWorld );

}

void Tools_Mode_VertexPainting::RenderPainting_Cube( const Asura_Vector_3& xMeshVertex, const uint32 uColour)
{
	SCOPEDPIXEVENT((0xffffffff, L"Render Cuboid"));
	
	if( s_uBufferCubeSize <= s_uBufferCubeCount)
	{
		BufferCubeFlush();
	}
	
	Asura_Bounding_Box xBox;
	
	xBox.SetToPoint( xMeshVertex);
	
	float fSize = 0.05f;
	xBox.Grow( fSize);
	
	D3DCubeVertex* pxVertices = s_pxBufferCubeVertices + s_uBufferCubeVertexCount;
	Asura_Assert( (pxVertices + 8) <= (s_pxBufferCubeVertices + s_uBufferCubeVertexSize), "Out of range array access");
	
	for(int i=0;i<8;i++)
	{
		D3DCubeVertex& xVertice = pxVertices[ i];
		
		xVertice.X = (i&1) ? xBox.MaxX : xBox.MinX;
		xVertice.Y = (i&2) ? xBox.MaxY : xBox.MinY;
		xVertice.Z = (i&4) ? xBox.MaxZ : xBox.MinZ;
		xVertice.Diffuse = uColour;
	}

	{
		static const uint16 uIndices[36] = 
		{
			0,4,6,//min x
				0,6,2,
				3,7,5,//max x
				3,5,1,
				1,5,4,//min y
				1,4,0,
				2,6,7,//max y
				2,7,3,
				0,2,3,//min z
				0,3,1,
				4,6,7,//max z
				4,7,5
		};
		
		uint16* pusIndices = s_pxBufferCubeIndices + s_uBufferCubeIndexCount;
		Asura_Assert( (pusIndices + 36) <= (s_pxBufferCubeIndices + s_uBufferCubeIndexSize), "Out of range array access");
		
		uint16 uIndexOffset = uint16( s_uBufferCubeVertexCount);
		for( u_int uIndexIndex = 0; uIndexIndex < 36; uIndexIndex += 1)
		{
			pusIndices[ uIndexIndex] = uIndices[ uIndexIndex] + uIndexOffset;
		}
	}
	
	s_uBufferCubeIndexCount += 36;
	s_uBufferCubeVertexCount += 8;
	s_uBufferCubeCount += 1;

}

///////////////////////////////////////////////////////////////////////////////////////
// Initialise / Close Overlay
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_VertexPainting::InitialiseOverlay()
{
	
	CloseOverlay();

	// create an overlay
	Asura_Overlay* pxOverlay = new Asura_Overlay( "Vertex Painting", false, &s_xOverlayPosition );
	pxOverlay->m_uID = TOOLS_OVERLAY_VERTEXPAINTING_MAIN;
	Asura_Assert( pxOverlay != NULL, "Cant create vertex painting overlay" );
	
	Asura_Vector_2 xOffset( 1.0f, 3.0f );
	Asura_Dialog* pxDialog;

	xOffset.x += 12.0f;
	pxDialog = new Tools_ColourPicker_Dialog( "Colour", xOffset, Asura_Vector_2( 4.0f, 3.0f ), &m_fColourRed, &m_fColourGreen, &m_fColourBlue, 255.0f, NULL );
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 12.0f;

	pxDialog = new Asura_Numerical_Dialog( "Red", xOffset, &m_fColourRed, 0.0f, 255.0f, 1.0f, "%0.0f" );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Green", xOffset, &m_fColourGreen, 0.0f, 255.0f, 1.0f, "%0.0f" );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Blue", xOffset, &m_fColourBlue, 0.0f, 255.0f, 1.0f, "%0.0f" );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	static const char* const pszOperationStrs[ NUM_OPS ] = { "Replace", "Add", "Sub", "Modulate" };
	pxDialog = new Asura_DropDown_List_Dialog( xOffset, pszOperationStrs, (int*)( &m_eOp ), NUM_OPS, NUM_OPS, NULL, NULL, "Operation" ); 
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Vert-Per-Poly Select", xOffset, &m_bVertexPerPolySelection );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog( "Apply", xOffset, _OnApply, NULL, _IsApplyEnabled );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Painting Active [P]", xOffset, &m_bPaintingActive );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Paint Radius", xOffset, &m_fPaintingRadius, 0.1f, 10.0f, 0.1f, "%.1f" );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog( "Reset All", xOffset, _ResetAll);
	pxDialog->SetHelpText("Removes all vertex painting values from all module meshes.");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog( "Undo [Ctrl+Z]", xOffset, _Undo);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog( "Redo [Ctrl+Y]", xOffset, _Redo);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog( "Hide Selected Module Verts", xOffset, _HideSelectedModules, NULL, NULL );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog( "Show All Module Verts", xOffset, _UnHideAllModules, NULL, NULL );
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Red", xOffset, &m_bPaintMaskR);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Green", xOffset, &m_bPaintMaskG);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Blue", xOffset, &m_bPaintMaskB);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	// submit the overlay
	Asura_Overlay_System::SubmitNewOverlay( pxOverlay, false );	

}


void Tools_Mode_VertexPainting::CloseOverlay()
{
	// if it exists already, shut it down
	if ( Asura_Overlay_System::DoesOverlayExist( TOOLS_OVERLAY_VERTEXPAINTING_MAIN ) )
	{
		Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_VERTEXPAINTING_MAIN );
	}
}


///////////////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_VertexPainting::OnApply()
{
	if ( m_bVertexPerPolySelection )
	{
		OnApplyVertexPerPoly();
	}
	else
	{
		OnApplyVertex();
	}
}


bool Tools_Mode_VertexPainting::IsApplyEnabled()
{
	if ( m_bPaintingActive )
	{
		return false;
	}

	if ( ( m_bVertexPerPolySelection ) && ( Tools_VertexPerPoly_Selection::CountSelectedVertexPerPolys() > 0 ) )
	{
		return true;
	}

	if ( ( !m_bVertexPerPolySelection ) && ( Tools_Vertex_Selection::GetNumberOfSelectedVertices() > 0 ) )
	{
		return true;
	}

	return false;

}


///////////////////////////////////////////////////////////////////////////////////////
// Functions
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_VertexPainting::OnApplyVertex()
{
	m_pxUndoBuffer->MarkUndoStart();

	Tools_DynamicArray<Asura_Mesh*> xMeshes;
	Tools_Vertex_Selection::StartIteration();

	
	const uint32 uMaskColour = this->GetRGBMask();
	
	ModuleVertexPair xVertex;
	while ( Tools_Vertex_Selection::GetNextSelected( &xVertex ) )
	{
		Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( xVertex.iModuleIndex );

		bool bMeshModified = false;

		for ( int i = 0; i < pxMesh->NumberOfPolygons; i++ )
		{
			for ( int j = 0; j < pxMesh->PolygonDataPtr[ i ].NumberOfVertices(); j++ )
			{
				if ( pxMesh->PolygonDataPtr[ i ].VertexID[ j ] == xVertex.iVertexIndex )
				{
					m_pxUndoBuffer->LogVertexPaint( xVertex.iModuleIndex, i, j, pxMesh->m_pxPaintedColourDataPtr[ i ].Colour[ j ] );
					const unsigned int uModulateColour = ( pxMesh->PrelightingDataValid ) ? pxMesh->PrelightingDataPtr[ i ].Colour[ j ] : 0x808080;
					ApplyTo( pxMesh->m_pxPaintedColourDataPtr[ i ].Colour[ j ], uModulateColour, uMaskColour );
					bMeshModified = true;
				}
			}
		}

		xMeshes += pxMesh;

		if(bMeshModified) Tools_SaveAsura::SetMeshModified(pxMesh);
	}

	// now invalidate all the prelighting data on the meshes
	for ( unsigned int u = 0; u < xMeshes.GetSize(); u++ )
	{
		xMeshes[ u ]->PrelightingDataValid = false;
	}

	// Also invalidate the GI solution 
	Asura_GI_Solution::InvalidateSamples();

}


void Tools_Mode_VertexPainting::OnApplyVertexPerPoly()
{
	Tools_DynamicArray<Asura_Mesh*> xMeshes;

	const uint32 uMaskColour = this->GetRGBMask();
	
	for( LIF<ModuleVertexPerPolyTriplet> xLIF( Tools_VertexPerPoly_Selection::GetList() ); !xLIF.done(); xLIF.next() )
	{
		const ModuleVertexPerPolyTriplet& xVert = xLIF();

		Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( xVert.m_iModuleIndex );
		
		const unsigned int uModulateColour = ( pxMesh->PrelightingDataValid ) ? pxMesh->PrelightingDataPtr[ xVert.m_iPolyIndex ].Colour[ xVert.m_iVertexIndex ] : 0x808080;
		ApplyTo( pxMesh->m_pxPaintedColourDataPtr[ xVert.m_iPolyIndex ].Colour[ xVert.m_iVertexIndex ], uModulateColour, uMaskColour );

		// invalidate the mesh afterwards

		xMeshes += pxMesh;

		Tools_SaveAsura::SetMeshModified(pxMesh);
	}
	

	// now invalidate all the prelighting data on the meshes
	for ( unsigned int u = 0; u < xMeshes.GetSize(); u++ )
	{
		xMeshes[ u ]->PrelightingDataValid = false;
	}

	// Also invalidate the GI solution 
	Asura_GI_Solution::InvalidateSamples();
}


const unsigned int Tools_Mode_VertexPainting::MakeColour( const int iR, const int iG, const int iB )
{
	return ( Asura_Maths::ClampToRange( iR, 0, 255 ) << 16 ) | ( Asura_Maths::ClampToRange( iG, 0, 255 ) << 8 ) | ( Asura_Maths::ClampToRange( iB, 0, 255 ) );
}


void Tools_Mode_VertexPainting::ApplyTo( unsigned int& uColourTarget, unsigned int uModulateColour, uint32 uMaskColour)
{
	const int iNewRed = static_cast<int>( m_fColourRed );
	const int iNewGreen = static_cast<int>( m_fColourGreen );
	const int iNewBlue = static_cast<int>( m_fColourBlue );

	int uColourNew = uColourTarget;

	switch ( m_eOp )
	{
		case OP_REPLACE:
		{
			uColourNew = MakeColour( iNewRed, iNewGreen, iNewBlue );
			break;
		}

		case OP_ADD:
		case OP_SUBTRACT:
		{
			const int iExistingRed = ( ( uColourNew >> 16 ) & 0xff );
			const int iExistingGreen = ( ( uColourNew >> 8 ) & 0xff );
			const int iExistingBlue = ( uColourNew & 0xff );

			uColourNew = ( m_eOp == OP_SUBTRACT ) ? 
				MakeColour( iExistingRed - iNewRed, iExistingGreen - iNewGreen, iExistingBlue - iNewBlue )
			: 
				MakeColour( iExistingRed + iNewRed, iExistingGreen + iNewGreen, iExistingBlue + iNewBlue );
			break;
		}

		case OP_MODULATE:
		{
			const int iModRed = ( ( uModulateColour >> 16 ) & 0xff );
			const int iModGreen = ( ( uModulateColour >> 8 ) & 0xff );
			const int iModBlue = ( uModulateColour & 0xff );
			uColourNew = MakeColour( ( iModRed + iNewRed ) / 2, ( iModGreen + iNewGreen ) / 2, ( iModBlue + iNewBlue ) / 2 );
			break;
		}
	}
	
	const uint32 uColourMatt = uColourTarget & (~uMaskColour);
	uColourNew &= uMaskColour;
	uColourTarget = uColourMatt | uColourNew;
}


void Tools_Mode_VertexPainting::InvalidateAllLighting()
{
	for ( int iModule = 0; iModule < Asura_Module_System::GetNumberOfModules(); iModule++ )
	{
		Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( iModule );
		pxMesh->PrelightingDataValid = false;
	}

	// Also invalidate the GI solution 
	Asura_GI_Solution::InvalidateSamples();
}

void Tools_Mode_VertexPainting::ResetAll()
{
	if(!ShowYesNoOverlay("This will remove all vertex colours from all modules.\nAre you sure you want to do this?")) return;

	m_pxUndoBuffer->MarkUndoStart();

	//Reset painted colours on all modules
	for( int i = 0; i < Asura_Module_System::GetNumberOfModules(); i++ )
	{
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( i );
		int iNumPolys=pxMesh->NumberOfPolygons;
					
		for (int j=0;j<iNumPolys;j++)
		{									
			//dont' attempt to alter polygons from merged objects
			if(pxMesh->IsMergedObjectPolygon(j)) continue;
			
			for ( int k = 0; k < pxMesh->PolygonDataPtr[ j ].NumberOfVertices(); k++ )
			{

				// Log undo
				m_pxUndoBuffer->LogVertexPaint( i, j, k, pxMesh->m_pxPaintedColourDataPtr[ j ].Colour[ k ] );

				//reset back to defaults
				pxMesh->m_pxPaintedColourDataPtr[ j ].Colour[ k ] = 0x808080; 

			}
		}
		pxMesh->PrelightingDataValid = false;
	}

	Tools_SaveAsura::SetMainFileModified();
}


void Tools_Mode_VertexPainting::HandleMeshChange(int iModuleIndex)
{
	//Invalidate any undos
	if(m_pxUndoBuffer) m_pxUndoBuffer->FlushUndoBuffer();
}

void Tools_Mode_VertexPainting::HideSelectedModules( )
{
	Tools_Vertex_Selection::StartIteration();

	ModuleVertexPair xVertex;
	while ( Tools_Vertex_Selection::GetNextSelected( &xVertex ) )
	{
		ModuleInformation& xModule = this->m_axModuleInformation[ xVertex.iModuleIndex];
		
		xModule.m_bVisible = false;
	}
}

void Tools_Mode_VertexPainting::UnHideAllModules( )
{
	const u_int uModuleCount = this->m_axModuleInformation.Size();
	for( u_int uModuleIndex = 0; uModuleIndex < uModuleCount; uModuleIndex += 1)
	{
		ModuleInformation& xModule = this->m_axModuleInformation[ uModuleIndex];
		
		xModule.m_bVisible = true;
	}
}

uint32 Tools_Mode_VertexPainting::GetRGBMask()
{
	uint32 uMask = 0xFF000000;
	
	if( true == this->m_bPaintMaskR)
	{
		uMask |= 0x00FF0000;
	}
	
	if( true == this->m_bPaintMaskG)
	{
		uMask |= 0x0000FF00;
	}
	
	if( true == this->m_bPaintMaskB)
	{
		uMask |= 0x000000FF;
	}
	
	return( uMask);
}
