//
// Tools_TextureAnimation.cpp
//
// Created by Richard Rice - 17/05/2005
// 
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Texturing/Tools_TextureAnimation.h"

#include "Animation/Asura_TextureAnimation.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Dialog.h"
#include "Mesh/Asura_Mesh.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Rendering/Asura_Module.h"

#include "Tools_Overlays.h"
#include "Load&Save/Tools_SaveAsura.h"
#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
#include "Materials/Tools_Overlay_PickMaterialResponse.h"
#include "Materials/Tools_MaterialResponse_System.h"
#endif
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Polygon_Selection.h"
#include "Misc/Tools_Render_Options.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Texturing/Tools_PickTexture.h"
#include "Texturing/Tools_Mode_Texturing.h"
#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
#include "Texturing/Tools_Texturing_MaterialResponse.h"
#endif
#include "Texturing/Tools_Texturing_Find.h"



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*--------------------------------------------**
** Tools_Overlay_FindOrCreateMaterialWithAnim **
**--------------------------------------------*/
#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM

class Tools_Overlay_FindOrCreateMaterialWithAnim : public Tools_Modal_Overlay
{
public :
	Tools_Overlay_FindOrCreateMaterialWithAnim( const Asura_String& xTitle, Tools_MaterialResponse* pxMaterial, Asura_Hash_ID uAnimID);
	~Tools_Overlay_FindOrCreateMaterialWithAnim();

	Asura_Hash_ID DoModal();

	bool IsSkipAllSelected(){return m_bSkipAll;}

protected :
	virtual void InitialiseOverlay(class Asura_Overlay* pxOverlay);
	void BuildExistingMaterialList();	

	static void ApplyAnimToThisMaterial();
	static void UseSelectedMaterial();
	static void CreateMaterial();
	static void Skip();
	static void SkipAll();
	

private :
	Tools_MaterialResponse* m_pxMaterial;
	Asura_Hash_ID m_uAnimID;

	int m_iExistingMaterialListSize;
	const char** m_pszExistingMaterials;
	bool m_bAnyExistingMaterialsFound;
	Asura_StringTree m_xMaterialNameTree;
	int m_iSelectedMaterial;

	Asura_Hash_ID m_uReturnValue;
	bool m_bSkipAll;

	static Tools_Overlay_FindOrCreateMaterialWithAnim* s_pxThis;

};

Tools_Overlay_FindOrCreateMaterialWithAnim* Tools_Overlay_FindOrCreateMaterialWithAnim::s_pxThis = 0;

Tools_Overlay_FindOrCreateMaterialWithAnim::Tools_Overlay_FindOrCreateMaterialWithAnim(const Asura_String& xTitle, Tools_MaterialResponse* pxMaterial, Asura_Hash_ID uAnimID)
:Tools_Modal_Overlay(xTitle)
{
	m_pxMaterial = pxMaterial;
	m_uAnimID = uAnimID;

	m_iExistingMaterialListSize = 0;
	m_pszExistingMaterials = 0;
	m_bAnyExistingMaterialsFound = false;
	m_iSelectedMaterial = 0;

	m_uReturnValue = 0;
	m_bSkipAll = false;

}

Tools_Overlay_FindOrCreateMaterialWithAnim::~Tools_Overlay_FindOrCreateMaterialWithAnim()
{
	delete [] m_pszExistingMaterials;
}

Asura_Hash_ID Tools_Overlay_FindOrCreateMaterialWithAnim::DoModal()
{
	m_uReturnValue = 0;

	s_pxThis = this;
	ShowOverlay();
	s_pxThis = 0;

	return m_uReturnValue;
}

void Tools_Overlay_FindOrCreateMaterialWithAnim::InitialiseOverlay(class Asura_Overlay* pxOverlay)
{
	Asura_Dialog* pxDialog;
	Asura_Vector_2 xOffset(1.0f,3.0f);
	
	//show the name and texture for the current material
	char szText[200];
	Asura_PrintfToBfr(szText,"%s :",sizeof(szText),true,m_pxMaterial->GetName().GetString());
	pxDialog = new Asura_Dialog(szText,xOffset);
	pxDialog->SetHelpText("The material being replaced");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Image_Dialog(xOffset, m_pxMaterial->GetTexture(Asura_MaterialResponse::ALBEDO_TEXTURE), 128.0f, 128.0f);
	pxDialog->SetHelpText("The material being replaced");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;



	//show a list of existing materials that use this anim
	BuildExistingMaterialList();
	m_iSelectedMaterial = 0;

	pxDialog = new Asura_Dialog("Existing Materials :",xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_List_Dialog(xOffset, m_pszExistingMaterials,&m_iSelectedMaterial, 8, m_iExistingMaterialListSize, NULL);
	pxDialog->SetHelpText("Existing materials that use the desired animation and have the correct albedo texture");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;


	pxDialog = new Asura_Function_Dialog("Apply To Current", xOffset, ApplyAnimToThisMaterial);
	pxDialog->SetHelpText("Applies the animation to the material currently being used.\nThis will mean that all polygons using the material will gain the animation , even if not selected.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = 15.0f;
	pxDialog = new Asura_Function_Dialog("Use Selected", xOffset, UseSelectedMaterial);
	pxDialog->SetEnabled(m_bAnyExistingMaterialsFound);
	pxDialog->SetHelpText("Use the material selected from the list above");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	xOffset.x = 1.0f;


	pxDialog = new Asura_Function_Dialog("Create Material", xOffset, CreateMaterial);
	pxDialog->SetHelpText("Create a new material using the animation");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog("Skip", xOffset, Skip);
	pxDialog->SetHelpText("Skip dealing with this material");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = 15.0f;
	pxDialog = new Asura_Function_Dialog("Skip All", xOffset, SkipAll);
	pxDialog->SetHelpText("Skip dealing with all the materials");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	xOffset.x = 1.0f;
}


void Tools_Overlay_FindOrCreateMaterialWithAnim::BuildExistingMaterialList()
{
	Tools_MaterialResponse_System::EnsureAllMaterialsAreLoaded();

	//Get a list of materials that use the correct texture animation and have a matching albedo texture.
	//May want to sort them according to closeness of match later I guess
	u_int uNumMaterials = Tools_MaterialResponse_System::GetNumMaterials();
	for(u_int i=0; i<uNumMaterials; i++)
	{
		Tools_MaterialResponse* pxOtherMaterial = Tools_MaterialResponse_System::GetMaterialByIndex(i);
		if(pxOtherMaterial->GetTextureAnim() != m_uAnimID) continue;
		if(pxOtherMaterial->GetTextureHash(Asura_MaterialResponse::ALBEDO_TEXTURE) != m_pxMaterial->GetTextureHash(Asura_MaterialResponse::ALBEDO_TEXTURE)) continue;

		int iJunkIndex;
		m_xMaterialNameTree.FindNodeIndexByName(pxOtherMaterial->GetName(),true,iJunkIndex);
	}

	m_iExistingMaterialListSize = m_xMaterialNameTree.GetNumberOfNodes();
	if(m_iExistingMaterialListSize)
	{
		m_pszExistingMaterials = new const char*[m_iExistingMaterialListSize];
		m_xMaterialNameTree.MakeSortedNameList(m_pszExistingMaterials);
		m_bAnyExistingMaterialsFound = true;
	}
	else
	{
		m_iExistingMaterialListSize = 1;
		m_pszExistingMaterials = new const char*[m_iExistingMaterialListSize];
		m_pszExistingMaterials[0] = "*None*";
		m_bAnyExistingMaterialsFound = false;
	}
}

void Tools_Overlay_FindOrCreateMaterialWithAnim::ApplyAnimToThisMaterial()
{
	s_pxThis->m_pxMaterial->SetTextureAnim(s_pxThis->m_uAnimID);
	s_pxThis->m_uReturnValue = s_pxThis->m_pxMaterial->GetHash();
	s_pxThis->CloseOverlay();

}

void Tools_Overlay_FindOrCreateMaterialWithAnim::UseSelectedMaterial()
{
	const char* szMaterialName = s_pxThis->m_pszExistingMaterials[s_pxThis->m_iSelectedMaterial];
	s_pxThis->m_uReturnValue = Asura_GetHashID(szMaterialName);
	s_pxThis->CloseOverlay();
}


void Tools_Overlay_FindOrCreateMaterialWithAnim::CreateMaterial()
{
	Asura_Hash_ID uMat = Tools_Texturing_MaterialResponse::DuplicateMaterial(s_pxThis->m_pxMaterial->GetHash(),false);
	if(uMat)
	{
		Tools_MaterialResponse* pxOtherMaterial = Tools_MaterialResponse_System::GetMaterialByHashID(uMat);
		if(pxOtherMaterial)
		{
			pxOtherMaterial->SetTextureAnim(s_pxThis->m_uAnimID);
			s_pxThis->m_uReturnValue = uMat;
			s_pxThis->CloseOverlay();
		}
	}
}

void Tools_Overlay_FindOrCreateMaterialWithAnim::Skip()
{
	s_pxThis->m_uReturnValue = 0;
	s_pxThis->CloseOverlay();
}

void Tools_Overlay_FindOrCreateMaterialWithAnim::SkipAll()
{
	s_pxThis->m_uReturnValue = 0;
	s_pxThis->m_bSkipAll = true;
	s_pxThis->CloseOverlay();
}

#endif //ASURA_USE_MATERIAL_RESPONSE_SYSTEM
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


bool Tools_TextureAnimation::s_bOverlayOpen = false;
Asura_Vector_2 Tools_TextureAnimation::s_xOverlayPosition(20,20);
Asura_Vector_2 Tools_TextureAnimation::s_xEditAnimOverlayPosition(40,20);

int Tools_TextureAnimation::s_iSelectedAnim = 0;
int Tools_TextureAnimation::s_iNumAnimNames = 0;
const char** Tools_TextureAnimation::s_ppszAnimNames = 0;

Asura_Hash_ID Tools_TextureAnimation::s_uEditedAnim = 0;

static const char* g_pszNoAnimString = "*None*";

int Tools_TextureAnimation::s_iSelectedSequence = 0;
int Tools_TextureAnimation::s_iSelectedFrame = 0;

bool Tools_TextureAnimation::s_bNeedUpdateFromAnim = false;
bool Tools_TextureAnimation::s_bChanged = false;
bool Tools_TextureAnimation::s_bDurationChanged = false;

float Tools_TextureAnimation::s_fSeqDuration;
bool Tools_TextureAnimation::s_bSeqLoop;
bool Tools_TextureAnimation::s_bSeqLoopBackAndForth;
bool Tools_TextureAnimation::s_bSeqInterpolate;
bool Tools_TextureAnimation::s_bSeqRandom;

float Tools_TextureAnimation::s_fFrameU;
float Tools_TextureAnimation::s_fFrameV;
float Tools_TextureAnimation::s_fFrameTime;
bool Tools_TextureAnimation::s_bFrameTimeInSecs = false;

u_int Tools_TextureAnimation::s_uFrameAddedMaterialFlags;
u_int Tools_TextureAnimation::s_uFrameRemovedMaterialFlags;

int Tools_TextureAnimation::s_iSelectedMaterialFlag;
int Tools_TextureAnimation::s_iMaterialFlagListSize;
char** Tools_TextureAnimation::s_pszMaterialFlagListStrings;

bool Tools_TextureAnimation::s_bUseUVScale = true;
bool Tools_TextureAnimation::s_bUseUVScaleChanged = false;
float Tools_TextureAnimation::s_fUVScale = 512.0f;

Asura_String Tools_TextureAnimation::s_xNumSequenceText;
Asura_String Tools_TextureAnimation::s_xNumFrameText;

bool Tools_TextureAnimation::s_bShowEditedFrame = false;

bool Tools_TextureAnimation::s_bShowEnvAnimsOnly = true;
bool Tools_TextureAnimation::s_bShowEnvAnimsOnlyChanged = false;

Asura_Message_TextureAnimation* Tools_TextureAnimation::s_pxEditedMessage = 0;

//list of anims that have been created in this tools session
static List<Asura_Hash_ID> g_xNewAnimList;

//List of material flags that can be affected by animations
//Only a subset of all material flags allowed , for various reasons.
//If flags are re-enabled ,then the functions for checking the flags in Asura_Material_System will need to be updated.

#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM

//material flags allowed for material response builds

#define NO_OF_MATERIAL_FLAGS (4)
static char* g_aszMaterialFlagNames[NO_OF_MATERIAL_FLAGS] =
{
	"Self Illuminate",
	"Don't Render",
	"No Collisions",
	"Ignored By ALL Line-Of-Sight",
};


static u_int g_auMaterialFlagValues[NO_OF_MATERIAL_FLAGS] =
{
	AsuraMaterialFlag_SelfIlluminate,
	AsuraMaterialFlag_DontRender,
	AsuraMaterialFlag_NoCollisions,
	AsuraMaterialFlag_IgnoreLOS,
};

#else

//material flags allowed for non material response builds

#define NO_OF_MATERIAL_FLAGS (11)
static char* g_aszMaterialFlagNames[NO_OF_MATERIAL_FLAGS] =
{
	"Self Illuminate",
//	"Transparent",	   //RAR - Disabled because it affects sorting on export
	"Use Detail Map",
	"Reflective",
//	"FMV Texture",	   //RAR - Disabled because it affects sorting on export
	"Flowing Water",
	"Waterfall",
//	"Subdivided Polygon", //RAR - Disabled because I don't think it makes sense to change this
//	"Additive Alpha",  //RAR - Disabled because it affects sorting on export
//	"3d Decal",		//RAR - Disabled because I don't think it makes sense to change this
	"Rain Splashes",
	"Specular - soft highlights",
	"Specular - sharp highlights",
//	"Alpha Decal (PSP only)", //RAR - Disabled because it affects sorting on export
	"Don't Render",
	"No Collisions",
	"Ignored By ALL Line-Of-Sight",
};


static u_int g_auMaterialFlagValues[NO_OF_MATERIAL_FLAGS] =
{
	AsuraMaterialFlag_SelfIlluminate,
//	AsuraMaterialFlag_Transparent,
	AsuraMaterialFlag_UseDetailMap,
	AsuraMaterialFlag_Reflective,
//	AsuraMaterialFlag_FMV,
	AsuraMaterialFlag_FlowingWater,
	AsuraMaterialFlag_Waterfall,
//	AsuraMaterialFlag_SubdividePolygon,
//	AsuraMaterialFlag_PS2AdditiveAlpha,
//	AsuraMaterialFlag_3dDecal,
	AsuraMaterialFlag_RainSplashes,
	AsuraMaterialFlag_Specular_Soft,
	AsuraMaterialFlag_Specular_Sharp,
//	AsuraMaterialFlag_AlphaDecal,
	AsuraMaterialFlag_DontRender,
	AsuraMaterialFlag_NoCollisions,
	AsuraMaterialFlag_IgnoreLOS,
};

#endif


void Tools_TextureAnimation::Initialise()
{
	Tools_TextureAnimation::CreateOverlay();
}

void Tools_TextureAnimation::Shutdown()
{
	Tools_TextureAnimation::CloseOverlays();
}

void Tools_TextureAnimation::Update()
{

	if(s_bShowEnvAnimsOnlyChanged)
	{
		//re-create overlay to update the list
		CloseOverlays();
		CreateOverlay();
	}

	if(s_bUseUVScaleChanged)
	{
		//recreate the overlay to update the UV boxes
		EditAnim(s_uEditedAnim);
	}
	else if(s_bNeedUpdateFromAnim)
	{
		UpdateOverlayVariablesFromAnim();
	}
	else if(s_bChanged || s_bDurationChanged)
	{
		UpdateAnimFromOverlayVariables();
		SetModified();
	}
	s_bNeedUpdateFromAnim = false;
	s_bDurationChanged = false;
	s_bChanged = false;
	s_bUseUVScaleChanged = false;

	//update the sequence in the current anim if needed
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(pxAnim)
	{
		bool bOverlayExists = Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_EDITTEXTUREANIMATION);
		
		if(bOverlayExists)
		{
			pxAnim->SetSequence(s_iSelectedSequence);
		}

		//need to show the edited frame?
		if(s_bShowEditedFrame && bOverlayExists)
		{
			pxAnim->SetFrameOverride(s_iSelectedFrame);
		}
		else
		{
			pxAnim->ClearFrameOverride();
		}
	}

}

void Tools_TextureAnimation::ToggleOverlay(void)
{
	s_bOverlayOpen = !s_bOverlayOpen;

	if (s_bOverlayOpen)
	{
		CreateOverlay();
	}
	else
	{
		CloseOverlays();
	}
}
bool Tools_TextureAnimation::OverlayIsOn(void)
{
	return s_bOverlayOpen;
}

void Tools_TextureAnimation::CreateOverlay(void)
{
	if(!s_bOverlayOpen) return;

	Asura_Overlay* pxOverlay;
	pxOverlay = new Asura_Overlay("Texture Animation",false,&s_xOverlayPosition);
	
	Asura_Dialog* pxDialog;
	Asura_Vector_2 xOffset = Asura_Vector_2(1.0f,3.0f);

	pxDialog = new Asura_Tickbox_Dialog("Show Env Anims Only",xOffset,&s_bShowEnvAnimsOnly,&s_bShowEnvAnimsOnlyChanged);
	pxDialog->SetHelpText("Limits anim list so that it only shows animations that are used on the environment (not including merged object polygons).\nNewly created animations will be shown regardless.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
	//add heading for anim list
	pxDialog = new Asura_Dialog("Animations :",xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::SmallSpacing();

	//add list of anim names
	BuildAnimNames();
	if(s_iSelectedAnim>=s_iNumAnimNames) s_iSelectedAnim = s_iNumAnimNames-1;
    	
	int iNumLines = Asura_Maths::Min(15,s_iNumAnimNames);
	pxDialog = new Asura_List_Dialog(xOffset,s_ppszAnimNames,&s_iSelectedAnim,iNumLines,s_iNumAnimNames,0);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;


	pxDialog = new Asura_Function_Dialog("Create",xOffset,CreateAnim);
	pxDialog->SetHelpText("Creates a new texture animation");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Duplicate",xOffset,DuplicateAnim);
	pxDialog->SetHelpText("Creates a copy of the selected texture animation.");
	pxDialog->SetIsEnabledFunction(IsAnimSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Delete",xOffset,DeleteAnim);
	pxDialog->SetHelpText("Deletes the currently selected texture animation");
	pxDialog->SetIsEnabledFunction(IsAnimSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Rename",xOffset,RenameAnim);
	pxDialog->SetHelpText("Renames the currently selected texture animation");
	pxDialog->SetIsEnabledFunction(IsAnimSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Edit",xOffset,EditAnim);
	pxDialog->SetHelpText("Edits the currently selected texture animation");
	pxDialog->SetIsEnabledFunction(IsAnimSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog("Apply",xOffset,ApplyPrimaryAnim);
	pxDialog->SetHelpText("Applies the current animation to the selected polygons");
	pxOverlay->AddDialog(pxDialog);

	#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	xOffset.x += 9.0f;
	pxDialog = new Asura_Function_Dialog("Secondary",xOffset,ApplySecondaryAnim);
	pxDialog->SetHelpText("Applies the current animation to the secondary material(s) on the selected polygons");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 9.0f;
	#endif
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Remove",xOffset,RemovePrimaryAnim);
	pxDialog->SetHelpText("Removes texture animation from the selected polygons");
	pxOverlay->AddDialog(pxDialog);

	#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	xOffset.x += 9.0f;
	pxDialog = new Asura_Function_Dialog("Secondary",xOffset,RemoveSecondaryAnim);
	pxDialog->SetHelpText("Removes texture animation from the secondary material(s) on the selected polygons");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 9.0f;
	#endif
	xOffset.y += Asura_Dialog::LargeSpacing();




	pxDialog = new Asura_Function_Dialog("Find",xOffset,FindAnim);
	pxDialog->SetHelpText("Finds all polygons using the selected animation.\nSelection options etc are set by the 'Find' overlay\n");
	pxDialog->SetIsEnabledFunction(IsAnimSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Restart All",xOffset,Asura_TextureAnimation_System::ResetAnimationControllers);
	pxDialog->SetHelpText("Resets the timers on all texture animations to zero.\n");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	Tools_Render_Options::AddDisableTextureAnimControl(pxOverlay,xOffset);

		
	pxOverlay->m_uID = TOOLS_OVERLAY_TEXTUREANIMATION;

	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);
}


void Tools_TextureAnimation::CloseOverlays()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_TEXTUREANIMATION);
	CloseEditAnimOverlay();
	DeleteAnimNames();
	DeleteMaterialFlagList();
	s_uEditedAnim = 0;
}


static int SortAnimNames(const void* p1,const void* p2)
{
	return stricmp(*(const char**)p1,*(const char**)p2);
}

void Tools_TextureAnimation::BuildAnimNames()
{

	//delete the old strings
	DeleteAnimNames();

	List<Asura_Hash_ID> xEnvAnimList;
	if(s_bShowEnvAnimsOnly)
	{
		#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		Asura_HashTree xMaterialTree;
		#endif


		//get a list of all anims that are used on non merged-object environmental polygons
		for(int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
		{
			Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);
			if(!pxMesh->TextureDataPtr) continue;

			for(int j=0;j<pxMesh->NumberOfPolygons;j++)
			{
				if(pxMesh->IsMergedObjectPolygon(j)) continue;

				#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				//check the anims used by the materials
				for(int iMatSlot = 0; iMatSlot<TOOLS_NUM_MATERIALRESPONSE_PER_POLY; iMatSlot++)
				{
					Asura_Hash_ID uMaterial = pxMesh->TextureDataPtr[j].m_auMaterialResponseHashID[iMatSlot];
					if(!uMaterial) continue;

					//checked this material yet?
					int iJunkIndex;
					if(xMaterialTree.FindNodeIndexByHash(uMaterial, true, iJunkIndex)) continue;

					const Tools_MaterialResponse* pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID(uMaterial);
					if(!pxMaterial) continue;

					Asura_Hash_ID uAnimHash = pxMaterial->GetTextureAnim();
					if(!uAnimHash) continue;
		
					if(!xEnvAnimList.contains(uAnimHash))
					{
						xEnvAnimList.add_entry(uAnimHash);
					}
				}
				#else
				//check what anim this polygon uses
				Asura_Hash_ID uAnimHash = Asura_Material_System::GetTextureAnimation(pxMesh->TextureDataPtr[j].m_iMaterialID);
				if(!uAnimHash) continue;

				if(!xEnvAnimList.contains(uAnimHash))
				{
					xEnvAnimList.add_entry(uAnimHash);
				}
				#endif
			
			}
			
		}
	}
	
	if(Asura_TextureAnimation_System::GetNumberOfAnimations())	
	{
		//get all the anim names
		s_iNumAnimNames = Asura_TextureAnimation_System::GetNumberOfAnimations();
		s_ppszAnimNames = new const char*[s_iNumAnimNames];

		int iAnimIndex = 0;
		for(int i=0;i<s_iNumAnimNames;i++,iAnimIndex++)
		{
			Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByIndex(iAnimIndex);
			if(s_bShowEnvAnimsOnly)
			{
				//check against the env anim list
				Asura_Hash_ID uAnimHash = Asura_GetHashID(pxAnim->m_xName.GetString());
				if(!xEnvAnimList.contains(uAnimHash) && !g_xNewAnimList.contains(uAnimHash))
				{
					i--;
					s_iNumAnimNames--;
					continue;
				}
			}

			s_ppszAnimNames[i] = pxAnim->m_xName.GetString();
		}
		
		if(!s_iNumAnimNames)
		{
			//failed to add any anims after all
			s_ppszAnimNames[0] = g_pszNoAnimString;
			s_iNumAnimNames = 1;
		}					    
		else
		{
			//and sort them
			qsort(s_ppszAnimNames,s_iNumAnimNames,sizeof(char*),SortAnimNames);
		}

	}
	else
	{
		//no animations for the list
		s_iNumAnimNames = 1;
		s_ppszAnimNames = new const char*[1];
		s_ppszAnimNames[0] = g_pszNoAnimString;
	}

	s_bShowEnvAnimsOnlyChanged = false;
}

void Tools_TextureAnimation::DeleteAnimNames()
{
	delete [] s_ppszAnimNames;
	s_ppszAnimNames = 0;
	s_iNumAnimNames = 0;
}

void Tools_TextureAnimation::CreateAnim()
{
	Asura_Assert(s_bOverlayOpen,"");


	//ask for a name
	GetString_Overlay xOverlay("Name for animation :","");

	if(!xOverlay.DoModal()) return;

	const char* pszName = xOverlay.GetString();
	if(!*pszName) return;
	// PAZ - don't allow '*None*' as a name
	if( stricmp( pszName, g_pszNoAnimString ) == 0 )
	{
		ShowMessageOverlay("Invalid animation name.");
		return;
	}

	AsuraFileType eFileType = Tools_SaveAsura::GetFileType();
	char szAnimName[100];
	if(eFileType == AsuraFile_Shapes || eFileType == AsuraFile_Skin)
	{
		//for skins and shapes we want to prefix the name of the file to help with
		//uniqueness of names
		const char* pszFilename = Tools_SaveAsura::GetNameWithoutExtension();
		pszFilename = Tools_FileSystem::StripPath(pszFilename);

		Asura_PrintfToBfr(szAnimName,"%s_%s",100,true,pszFilename,pszName);
				
	}
	else
	{
		Asura_PrintfToBfr(szAnimName,"%s",100,true,pszName);
	}


	//Attempt to create the anim
	Asura_TextureAnimation* pxAnim;
	if(!Asura_TextureAnimation_System::NewAnimation(szAnimName,pxAnim))
	{
		//Already exists , or failed to create.
		if(pxAnim)
		{
			ShowMessageOverlay("An animation with that name already exists.");
		}
		else
		{
			ShowMessageOverlay("Unable to create any more animations.");
		}
		return;
	}

	//create a default sequence
	pxAnim->CreateDefaultSequence();

	//keep track of newly created anims so they don't get filtered out of the anim list
	g_xNewAnimList.add_entry(Asura_GetHashID(szAnimName));

	//re-create the overlay to update the list
	CloseOverlays();
	CreateOverlay();
		
	//Select the newly created anim from the list
	s_iSelectedAnim = GetListIndexFromName(szAnimName);
	Asura_Assert(s_iSelectedAnim>=0,"Failed to find anim in list?");

	SetModified();

	//edit the newly created anim
	EditAnim();
}

void Tools_TextureAnimation::DeleteAnim()
{
	Asura_Assert(s_bOverlayOpen,"");
	
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];

	//confirm deletion
	if(!ShowYesNoOverlay("Delete this animation?\n'%s'",pszAnimName))
	{
		return;
	}

	//and delete
	Asura_Hash_ID uHash = Asura_GetHashID(pszAnimName);
	Asura_TextureAnimation_System::DeleteAnimation(uHash);
	if(g_xNewAnimList.contains(uHash))
	{
		g_xNewAnimList.delete_entry(uHash);
	}

	//update the overlays
	CloseOverlays();
	CreateOverlay();

	SetModified();
}

void Tools_TextureAnimation::DuplicateAnim()
{
	Asura_Assert(s_bOverlayOpen,"");
	
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	//get the anim being copied from
	const char* pszSelectedName = s_ppszAnimNames[s_iSelectedAnim];
	Asura_Hash_ID uSelectedHash = Asura_GetHashID(pszSelectedName);
	const Asura_TextureAnimation* pxSelectedAnim = Asura_TextureAnimation_System::GetAnimationByHash(uSelectedHash);
	if(!pxSelectedAnim) return;
	


	//get name for copy
	GetString_Overlay xOverlay("Enter name for copy :","");

	if(!xOverlay.DoModal()) return;

	const char* pszName = xOverlay.GetString();
	if(!*pszName) return;
	// PAZ - don't allow '*None*' as a name
	if( stricmp( pszName, g_pszNoAnimString ) == 0 )
	{
		ShowMessageOverlay("Invalid animation name.");
		return;
	}

	AsuraFileType eFileType = Tools_SaveAsura::GetFileType();
	char szAnimName[100];
	if(eFileType == AsuraFile_Shapes || eFileType == AsuraFile_Skin)
	{
		//for skins and shapes we want to prefix the name of the file to help with
		//uniqueness of names
		const char* pszFilename = Tools_SaveAsura::GetNameWithoutExtension();
		pszFilename = Tools_FileSystem::StripPath(pszFilename);

		Asura_PrintfToBfr(szAnimName,"%s_%s",100,true,pszFilename,pszName);
				
	}
	else
	{
		Asura_PrintfToBfr(szAnimName,"%s",100,true,pszName);
	}


	//Attempt to create the anim
	Asura_TextureAnimation* pxAnim;
	if(!Asura_TextureAnimation_System::NewAnimation(szAnimName,pxAnim))
	{
		//Already exists , or failed to create.
		if(pxAnim)
		{
			ShowMessageOverlay("An animation with that name already exists.");
		}
		else
		{
			ShowMessageOverlay("Unable to create any more animations.");
		}
		return;
	}


	//copy the anim
	pxAnim->Copy(pxSelectedAnim);
	

	//keep track of newly created anims so they don't get filtered out of the anim list
	g_xNewAnimList.add_entry(Asura_GetHashID(szAnimName));

	//re-create the overlay to update the list
	CloseOverlays();
	CreateOverlay();
		
	//Select the newly created anim from the list
	s_iSelectedAnim = GetListIndexFromName(szAnimName);
	Asura_Assert(s_iSelectedAnim>=0,"Failed to find anim in list?");

	SetModified();

	//edit the newly created anim
	EditAnim();
	
}

bool Tools_TextureAnimation::IsAnimSelected()
{
	if( Asura_TextureAnimation_System::GetNumberOfAnimations() <= 0 )
	{
		return( false );
	}
	// PAZ - no list, or invalid selection
	if( ( s_ppszAnimNames == NULL ) || ( s_iNumAnimNames <= 0 ) || ( s_iSelectedAnim >= s_iNumAnimNames ) )
	{
		return( false );
	}
	// PAZ - ignore '*None*'
	const char* pszSelectedName = s_ppszAnimNames[ s_iSelectedAnim ];
	if( strcmp( pszSelectedName, g_pszNoAnimString ) == 0 )
	{
		return( false );
	}
	return( true );
}

void Tools_TextureAnimation::ApplyPrimaryAnim()
{
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];
	Asura_Hash_ID uHash = Asura_GetHashID(pszAnimName);

	ApplyAnim(uHash, 0);
}

void Tools_TextureAnimation::ApplySecondaryAnim()
{
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];
	Asura_Hash_ID uHash = Asura_GetHashID(pszAnimName);

	ApplyAnim(uHash, 1);
}


void Tools_TextureAnimation::RemovePrimaryAnim()
{
	ApplyAnim(0, 0);
}

void Tools_TextureAnimation::RemoveSecondaryAnim()
{
	ApplyAnim(0, 1);
}


#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
void Tools_TextureAnimation::ApplyAnim(Asura_Hash_ID uAnimHash, int iMaterialSlot)
{
	if(!Tools_Polygon_Selection::NumSelectedModules)
	{
		return;
	}

	//Get a list of all materials used by the selected polygons
	List<Asura_Hash_ID> xMatList;

	PolygonID xPoly;
	Tools_Polygon_Selection::StartIteration();

	while(Tools_Polygon_Selection::GetNextSelected(&xPoly))
	{
		//get the mesh data
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(xPoly.ModuleIndex);
		Asura_Assert(pxMesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");
		
		//get the texture info
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[xPoly.PolyIndex];
		Asura_Hash_ID uMaterialHash = pxTex->m_auMaterialResponseHashID[iMaterialSlot];
		if(!uMaterialHash) continue;

		if(!xMatList.contains(uMaterialHash))
		{
			xMatList.add_entry(uMaterialHash);
		}
	}

	//update the materials
	for(LIF<Asura_Hash_ID> xMatLIF(&xMatList);!xMatLIF.done();xMatLIF.next())
	{

		Tools_MaterialResponse* pxMat = Tools_MaterialResponse_System::GetMaterialByHashID(xMatLIF());
		if(!pxMat)
		{
			//Missing material , just ignore these for now , may want better handling later
			continue;
		}

		//already using the correct animation?
		if(pxMat->GetTextureAnim() == uAnimHash) continue;

		//show overlay for doing

		Tools_Overlay_FindOrCreateMaterialWithAnim xOverlay("Create/select material for texture anim.",pxMat,uAnimHash);
		Asura_Hash_ID uNewMatHash = xOverlay.DoModal();

		if(xOverlay.IsSkipAllSelected()) break;
		if(!uNewMatHash) continue;

	


		if(uNewMatHash && uNewMatHash != xMatLIF())
		{


			//update all the selected polygons that used this material
			Tools_Polygon_Selection::StartIteration();

			while(Tools_Polygon_Selection::GetNextSelected(&xPoly))
			{
				//get the mesh data
				Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(xPoly.ModuleIndex);
				
				//get the texture info
				Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[xPoly.PolyIndex];
				if(pxTex->m_auMaterialResponseHashID[iMaterialSlot] == xMatLIF())
				{
					pxTex->m_auMaterialResponseHashID[iMaterialSlot] = uNewMatHash;

					Tools_UpdateTexturingOnSurfaces(pxMesh);
				}
			}
		}
	}

	Tools_SaveAsura::SetMainFileModified();
	
}

#else //!ASURA_USE_MATERIAL_RESPONSE_SYSTEM

void Tools_TextureAnimation::ApplyAnim(Asura_Hash_ID uAnimHash, int /*iMaterialSlot*/)
{
	if(!Tools_Polygon_Selection::NumSelectedModules)
	{
		return;
	}

	//Get a list of all materials used by the selected polygons
	List<int> xMatList;

	PolygonID xPoly;
	Tools_Polygon_Selection::StartIteration();

	while(Tools_Polygon_Selection::GetNextSelected(&xPoly))
	{
		//get the mesh data
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(xPoly.ModuleIndex);
		Asura_Assert(pxMesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");
		
		//get the texture info
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[xPoly.PolyIndex];
		if(pxTex->m_iMaterialID == ASURA_MATERIAL_NONE) continue;

		if(!xMatList.contains(pxTex->m_iMaterialID))
		{
			xMatList.add_entry(pxTex->m_iMaterialID);
		}
	}

	bool bAskAboutCreateMaterial = true;
	bool bShouldCreateMaterials = false;

	//update the materials
	for(LIF<int> xMatLIF(&xMatList);!xMatLIF.done();xMatLIF.next())
	{
		
		bool bCreateNewMaterial = false;
		if(bAskAboutCreateMaterial || bShouldCreateMaterials)
		{
			bool bFoundMaterial = false;
			//Find if any unselected polygon uses this material
			for(int i=0;i<Asura_Module_System::GetNumberOfModules() && !bFoundMaterial;i++)
			{
				Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);
				Asura_Assert(pxMesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");
				
				for(int j=0;j<pxMesh->NumberOfPolygons;j++)
				{
					Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[j];
					if(pxTex->m_iMaterialID == xMatLIF())
					{
						if(!Tools_Polygon_Selection::IsSelected(PolygonID(i,j)))
						{
							bFoundMaterial = true;
							break;
						}
					}
				}
			}

			if(bFoundMaterial)
			{
				if(bAskAboutCreateMaterial)
				{
					bShouldCreateMaterials = ShowYesNoOverlay("Some of the materials on the selection are also used by non-selected polygons.\nWould you like to create a new version of the materials for these polygons?\nIf not , then the unselected polygons will have their anim changed as well.");
					bAskAboutCreateMaterial = false;
				}
				bCreateNewMaterial = bShouldCreateMaterials;
			}
		}


		Asura_Material* pxMat = Asura_Material_System::GetMaterial(xMatLIF());
		Asura_Assert(pxMat,"");

		if(bCreateNewMaterial)
		{
			//need to create a copy of the material
			Asura_Material xDesiredMat = *pxMat;
			xDesiredMat.SetTextureAnim(uAnimHash);

			int iNewMatID;
			bool bIsNew;
			if(Tools_PickTexture::CreateOrFindExistingMaterial(&xDesiredMat,false,iNewMatID,&bIsNew))
			{

				//if the original material already had the anim , then don't need to do anything
				if(iNewMatID == xMatLIF()) continue;

				if(bIsNew)
				{
					//update the material name from the anim name
					const Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(uAnimHash);
					Asura_Material* pxMat = Asura_Material_System::GetMaterial(iNewMatID);

					if(pxAnim && pxMat)
					{
						char szName[200];
						strcpy(szName,pxAnim->GetName());
						
						//add [Tx] postfix if needed
						if(!pxMat->m_xMaterialName.IsBlank())
						{
							char* pcEnd = strstr(pxMat->m_xMaterialName.GetString(),"[Tx");
							if(pcEnd)
							{
								strcat(szName,pcEnd);
							}
						}
						pxMat->m_xMaterialName = szName;

					}
				}

				//update all the selected polygons that used this material
				Tools_Polygon_Selection::StartIteration();

				while(Tools_Polygon_Selection::GetNextSelected(&xPoly))
				{
					//get the mesh data
					Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(xPoly.ModuleIndex);
					
					//get the texture info
					Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[xPoly.PolyIndex];
					if(pxTex->m_iMaterialID == xMatLIF())
					{
						pxTex->m_iMaterialID = iNewMatID;

						Tools_UpdateTexturingOnSurfaces(pxMesh);
					}
				}
			}

		}
		else
		{
			//we can alter the existing material
			pxMat->SetTextureAnim(uAnimHash);
		}
	}

	Tools_SaveAsura::SetMainFileModified();
	
}

#endif //!ASURA_USE_MATERIAL_RESPONSE_SYSTEM



void Tools_TextureAnimation::EditAnim()
{
	Asura_Assert(s_bOverlayOpen,"");
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];
	EditAnim(Asura_GetHashID(pszAnimName));

}

void Tools_TextureAnimation::EditAnim(Asura_Hash_ID uHash)
{
	CloseEditAnimOverlay();


	//get the selected anim
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(uHash);
	Asura_Assert(pxAnim,"");

	if(uHash != s_uEditedAnim)
	{
		//and reset the edited seq/frame
		s_iSelectedSequence = 0;
		s_iSelectedFrame = 0;
		s_uEditedAnim = uHash;
	}

	UpdateOverlayVariablesFromAnim();

	char szTitle[30];
	strncpy(szTitle,pxAnim->GetName(),20);
	if(strlen(pxAnim->GetName())>=20)
	{
		strcpy(&szTitle[17],"...");
	}
	
	Asura_Overlay* pxOverlay;
	pxOverlay = new Asura_Overlay(szTitle,false,&s_xEditAnimOverlayPosition);

	Asura_Dialog* pxDialog;
	Asura_Vector_2 xOffset = Asura_Vector_2(1.0f,3.0f);

	pxDialog = new Asura_Numerical_Dialog("Sequence",xOffset,&s_iSelectedSequence,-1,999,1,"%d",&s_bNeedUpdateFromAnim);
	pxDialog->SetHelpText("Selects the sequence to edit");
	pxOverlay->AddDialog(pxDialog);
	
	xOffset.x = pxDialog->m_xMaxCoord.x;
	pxDialog = new Asura_Dynamic_Dialog(&s_xNumSequenceText,xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("New Seq",xOffset,CreateSeq);
	pxDialog->SetHelpText("Create a new sequence");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = pxDialog->m_xMaxCoord.x+1.0f;
	pxDialog = new Asura_Function_Dialog("Remove",xOffset,DeleteSeq);
	pxDialog->SetHelpText("Delete the current sequence");
	pxDialog->SetIsEnabledFunction(IsEnabled_DeleteSeq);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Move Down",xOffset,MoveSeqEarlier);
	pxDialog->SetHelpText("Reorders the sequences , swapping the current sequence with the one before.");
	pxDialog->SetIsEnabledFunction(IsEnabled_MoveSeqEarlier);
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = pxDialog->m_xMaxCoord.x+1.0f;
	pxDialog = new Asura_Function_Dialog("Move Up",xOffset,MoveSeqLater);
	pxDialog->SetHelpText("Reorders the sequences , swapping the current sequence with the one after.");
	pxDialog->SetIsEnabledFunction(IsEnabled_MoveSeqLater);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();



	pxDialog = new Asura_Numerical_Dialog("Duration",xOffset,&s_fSeqDuration,0.1f,9999.0f,1.0f,"%.3f",&s_bDurationChanged);
	pxDialog->SetHelpText("Length of one loop of the sequence in seconds.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	
	pxDialog = new Asura_Tickbox_Dialog("Looped",xOffset,&s_bSeqLoop,&s_bChanged);	
	pxDialog->SetHelpText("Sequence loops");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Loop Back And Forth",xOffset,&s_bSeqLoopBackAndForth,&s_bChanged);	
	pxDialog->SetHelpText("Sequence reverses upon reaching the end and goes forward again upon returing to the start.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Tickbox_Dialog("Interpolate",xOffset,&s_bSeqInterpolate,&s_bChanged);	
	pxDialog->SetHelpText("Interpolate between frames");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Random",xOffset,&s_bSeqRandom,&s_bChanged);	
	pxDialog->SetHelpText("If random then the UV offsets are picked randomly every game frame up to the values specificed in the animation frame.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	
	pxDialog = new Asura_Numerical_Dialog("Frame",xOffset,&s_iSelectedFrame,-1,999,1,"%d",&s_bNeedUpdateFromAnim);
	pxDialog->SetHelpText("Selects the frame to edit");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = pxDialog->m_xMaxCoord.x;
	pxDialog = new Asura_Dynamic_Dialog(&s_xNumFrameText,xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Add After",xOffset,CreateFrame);
	pxDialog->SetHelpText("Add a new frame after the current one.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = pxDialog->m_xMaxCoord.x+1.0f;
	pxDialog = new Asura_Function_Dialog("Remove",xOffset,DeleteFrame);
	pxDialog->SetHelpText("Delete the current frame");
	pxDialog->SetIsEnabledFunction(IsEnabled_DeleteFrame);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_Tickbox_Dialog("Show Edited Frame",xOffset,&s_bShowEditedFrame);
	pxDialog->SetHelpText("Forces the currently edited frame to be shown regardless of the state of the animation timer (to help with setting the UV offsets).\nThis doesn't do anything if animations have been disabled.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	//Tab control
	static char* s_apszTabs[2] = {"UV Anim","Flag Anim"};
	Asura_Tabs_Dialog* pxTabsDialog = new Asura_Tabs_Dialog(xOffset,s_apszTabs,2);
	pxOverlay->AddDialog(pxTabsDialog);
	xOffset.y = pxTabsDialog->m_xMaxCoord.y + 1.0f;

	//redirect all subsequent dialogs to the tab dialog
	pxOverlay->SetCurrentParentDialog(pxTabsDialog);	
	

	float fTabsStartY = xOffset.y;
	float fTabsEndY = xOffset.y;

	//UV Anim Tab
	pxTabsDialog->SelectTab(0);

	pxDialog = new Asura_Tickbox_Dialog("Use UV Scale",xOffset,&s_bUseUVScale,&s_bUseUVScaleChanged);
	pxDialog->SetHelpText("Determines whether the UV offsets are measured in pixels (if on) or as floating point numbers.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("UV Scale",xOffset,&s_fUVScale,1.0f,9999.0f,1.0f,"%.0f",&s_bNeedUpdateFromAnim);
	pxDialog->SetHelpText("This scale value is used so that the offsets above are displayed in pixels.\nThis essentially defines the width/height of the texture.\n(This is purely for tools purposes , and is not stored in the animation).");
	pxDialog->SetEnabled(s_bUseUVScale);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	if(s_bUseUVScale)
		pxDialog = new Asura_Numerical_Dialog("U Offset",xOffset,&s_fFrameU,-9999.0f,9999.0f,1,"%.0f",&s_bChanged);
	else
		pxDialog = new Asura_Numerical_Dialog("U Offset",xOffset,&s_fFrameU,-9.0f,9.0f,1,"%.3f",&s_bChanged);
	pxDialog->SetHelpText("U offset for this frame.\nIf Use UV Scale is enabled then this is effectively measured in pixels.\nIf not then a value of 1 is equivalent to one repeat of the texture.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	if(s_bUseUVScale)
		pxDialog = new Asura_Numerical_Dialog("V Offset",xOffset,&s_fFrameV,-9999.0f,9999.0f,1,"%.0f",&s_bChanged);
	else
		pxDialog = new Asura_Numerical_Dialog("V Offset",xOffset,&s_fFrameV,-9.0f,9.0f,1,"%.3f",&s_bChanged);
	pxDialog->SetHelpText("V offset for this frame.\nIf Use UV Scale is enabled then this is effectively measured in pixels.\nIf not then a value of 1 is equivalent to one repeat of the texture.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	if(xOffset.y > fTabsEndY) fTabsEndY = xOffset.y;

	//Flag Anim Tab
	pxTabsDialog->SelectTab(1);
	xOffset.y = fTabsStartY;

	pxDialog = new Asura_Function_Dialog("Added Flag",xOffset,AddMaterialFlag);
	pxDialog->SetHelpText("Select a flag to be added during this frame");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Removed Flag",xOffset,RemoveMaterialFlag);
	pxDialog->SetHelpText("Select a flag to be removed during this frame");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Delete",xOffset,DeleteMaterialFlag);
	pxDialog->SetHelpText("Deletes the selected entry from the list of affected flags");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	BuildMaterialFlagList();
	pxDialog = new Asura_List_Dialog(xOffset,s_pszMaterialFlagListStrings,&s_iSelectedMaterialFlag,4,s_iMaterialFlagListSize,0);
	pxDialog->m_iControlID = Asura_GetHashID("MaterialFlagList");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;

	if(xOffset.y > fTabsEndY) fTabsEndY = xOffset.y;

	//end of tabs
	pxOverlay->SetCurrentParentDialog(0);
	static u_int s_uSelectedTab = 0;
	pxTabsDialog->SetSelectedTabPointer(&s_uSelectedTab);
	xOffset.y = fTabsEndY + 1.0f;

	

	pxDialog = new Asura_Tickbox_Dialog("Time In Secs",xOffset,&s_bFrameTimeInSecs,&s_bNeedUpdateFromAnim);
	pxDialog->SetHelpText("If on then frame times are measured in seconds, otherwise they are measured in the range 0 to 1");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Numerical_Dialog("Time",xOffset,&s_fFrameTime,0.0f,9999.0f,1,"%.3f",&s_bChanged);
	pxDialog->SetIsEnabledFunction(IsEnabled_FrameTime);
	pxDialog->SetHelpText("This is either measured as a value between 0 and 1 , or as a time in seconds depending on tickbox.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	


	pxOverlay->m_uID = TOOLS_OVERLAY_EDITTEXTUREANIMATION;
	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,true);
		
}

void Tools_TextureAnimation::CloseEditAnimOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_EDITTEXTUREANIMATION);
}

void Tools_TextureAnimation::RenameAnim()
{
	Asura_Assert(s_bOverlayOpen,"");
	if(!Asura_TextureAnimation_System::GetNumberOfAnimations()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];
	Asura_Hash_ID uOldHash = Asura_GetHashID(pszAnimName);
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(uOldHash);

	//ask for the new name for the anim
	GetString_Overlay xOverlay("Enter new name for the anim",pszAnimName);
	if(!xOverlay.DoModal()) return;

	//check the name is okay
	Asura_String xNewName = xOverlay.GetString();
	if(xNewName.IsBlank()) return;

	if(!strcmp(xNewName.GetString(),pszAnimName)) return;//name unchanged

	// PAZ - don't allow '*None*' as a name
	if( stricmp( xNewName.GetString(), g_pszNoAnimString ) == 0 )
	{
		ShowMessageOverlay("Invalid animation name.");
		return;
	}


	
	Asura_Hash_ID uNewHash = Asura_GetHashID(xNewName.GetString());
	if(uNewHash == uOldHash)
	{
		//Name is different , but has same hash (probably changed capitalisation.
		//So can get just update the name without doing anything else
		pxAnim->SetName(xNewName.GetString());

		//re-create the overlay to update the list
		CloseOverlays();
		CreateOverlay();
	}
	else
	{
		//hash already used?
		if(Asura_TextureAnimation_System::GetAnimationByHash(uNewHash))
		{
			ShowMessageOverlay("An animation with that name already exists");
			return;
		}

		//go ahead and rename then
		bool bOkay = Asura_TextureAnimation_System::RenameAnimation(pszAnimName,xNewName.GetString());
		if(!bOkay)
		{
			Asura_Assert(0,"Unexpected error renaming animation");
			return;
		}

		if(g_xNewAnimList.contains(uOldHash))
		{
			g_xNewAnimList.delete_entry(uOldHash);
			g_xNewAnimList.add_entry(uNewHash);
		}
		
		//re-create the overlay to update the list
		CloseOverlays();
		CreateOverlay();

		//ask whether to update the materials that use the animation
		bool bUpdate = ShowYesNoOverlay("Would you like to update the materials that currently use this animation?\nIf this isn't done ,then they will be left with unknown animations until a new one gets created with the old name.");

		if(bUpdate)
		{
			Asura_Material_System::ReplaceTextureAnimation(uOldHash,uNewHash);
		}
		
	}

	
		
	//Find the index under the new name
	s_iSelectedAnim = GetListIndexFromName(xNewName.GetString());
	Asura_Assert(s_iSelectedAnim>=0,"Failed to find anim in list?");

	SetModified();

}


void Tools_TextureAnimation::CreateSeq()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	//create a new sequence and select it
	pxAnim->AddSequence();
	s_iSelectedSequence = (int) pxAnim->m_uNumberOfSequences-1;
	s_iSelectedFrame = 0;

	//and update the overlay
	UpdateOverlayVariablesFromAnim();

	SetModified();	
}

void Tools_TextureAnimation::DeleteSeq()
{
	if(!IsEnabled_DeleteSeq()) return;

	if(!ShowYesNoOverlay("Delete this sequence?")) return;

	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	//delete the sequence
	pxAnim->DeleteSequence(s_iSelectedSequence);

	//and update the overlay
	UpdateOverlayVariablesFromAnim();

	SetModified();	
}

bool Tools_TextureAnimation::IsEnabled_DeleteSeq()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return false;

	return pxAnim->m_uNumberOfSequences > 1;	
}

void Tools_TextureAnimation::MoveSeqEarlier()
{
	if(!IsEnabled_MoveSeqEarlier()) return;

	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	//swap the current sequence with the one before
	Asura_TextureAnimationSequence xTemp;
	xTemp.Copy(&pxAnim->m_pxSequences[s_iSelectedSequence]);
	pxAnim->m_pxSequences[s_iSelectedSequence].Copy(&pxAnim->m_pxSequences[s_iSelectedSequence-1]);
	pxAnim->m_pxSequences[s_iSelectedSequence-1].Copy(&xTemp);

	//select the previous sequence
	s_iSelectedSequence--;
	SetModified();
}

bool Tools_TextureAnimation::IsEnabled_MoveSeqEarlier()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return false;

	return s_iSelectedSequence > 0;

}

void Tools_TextureAnimation::MoveSeqLater()
{
	if(!IsEnabled_MoveSeqLater()) return;

	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	//swap the current sequence with the one after
	Asura_TextureAnimationSequence xTemp;
	xTemp.Copy(&pxAnim->m_pxSequences[s_iSelectedSequence]);
	pxAnim->m_pxSequences[s_iSelectedSequence].Copy(&pxAnim->m_pxSequences[s_iSelectedSequence+1]);
	pxAnim->m_pxSequences[s_iSelectedSequence+1].Copy(&xTemp);

	//select the next sequence
	s_iSelectedSequence++;
	SetModified();
}

bool Tools_TextureAnimation::IsEnabled_MoveSeqLater()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return false;

	return s_iSelectedSequence+1 < (int)pxAnim->m_uNumberOfSequences;

}


void Tools_TextureAnimation::CreateFrame()
{
	//find the sequence
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;
	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences || s_iSelectedSequence<0) return;

	Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];

	//create a new frame and select it
	pxSeq->AddFrameAfter(s_iSelectedFrame);
	s_iSelectedFrame++;

	//and update the overlay
	UpdateOverlayVariablesFromAnim();

	SetModified();	
}

void Tools_TextureAnimation::DeleteFrame()
{
	if(!IsEnabled_DeleteFrame()) return;

	if(!ShowYesNoOverlay("Delete this frame?")) return;

	//find the sequence
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;
	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences || s_iSelectedSequence<0) return;

	Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];

	//delete the frame
	pxSeq->DeleteFrame(s_iSelectedFrame);

	//and update the overlay
	UpdateOverlayVariablesFromAnim();

	SetModified();	
}

bool Tools_TextureAnimation::IsEnabled_DeleteFrame()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return false;
	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences || s_iSelectedSequence < 0) return false;

	const Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];

	return pxSeq->m_uNumberOfFrames > 1;
}


void Tools_TextureAnimation::SetModified()
{
	Tools_SaveAsura::SetMainFileModified();
}


int Tools_TextureAnimation::GetListIndexFromName(const char* pszName)
{
	for(int i=0;i<s_iNumAnimNames;i++)
	{
		if(!stricmp(pszName,s_ppszAnimNames[i]))
		{
			return i;
		}
	}
	//failed to find
	return -1;
}

void Tools_TextureAnimation::UpdateOverlayVariablesFromAnim()
{
	const Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	//ensure sequence in range
	if(s_iSelectedSequence < 0) s_iSelectedSequence = (int)pxAnim->m_uNumberOfSequences-1;
	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences) s_iSelectedSequence = 0;

	//grab the sequence variables
	const Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];
	s_fSeqDuration = pxSeq->m_fDuration;
	s_bSeqLoop = pxSeq->m_bLoop;
	s_bSeqLoopBackAndForth = pxSeq->m_bLoopBackAndForth;
	s_bSeqInterpolate = pxSeq->m_bInterpolate;
	s_bSeqRandom = pxSeq->m_bRandom;

	//ensure frame in range
	if(s_iSelectedFrame < 0) s_iSelectedFrame = (int)pxSeq->m_uNumberOfFrames-1;
	if(s_iSelectedFrame >= (int)pxSeq->m_uNumberOfFrames) s_iSelectedFrame = 0;

	//grab the frame variables
	const Asura_TextureAnimationFrame* pxFrame = &pxSeq->m_pxFrames[s_iSelectedFrame];
	if(s_bUseUVScale)
	{
		s_fFrameU = (float)Asura_Maths::FloatToInt_Near(pxFrame->m_fU*s_fUVScale);
		s_fFrameV = (float)Asura_Maths::FloatToInt_Near(pxFrame->m_fV*s_fUVScale);
	}
	else
	{
		s_fFrameU = pxFrame->m_fU;
		s_fFrameV = pxFrame->m_fV;
	}
	if(s_bFrameTimeInSecs)
	{
		s_fFrameTime = pxFrame->m_fTime*s_fSeqDuration;
	}
	else
	{
		s_fFrameTime = pxFrame->m_fTime;
	}

	s_uFrameAddedMaterialFlags = pxFrame->m_uAddedMaterialFlags;
	s_uFrameRemovedMaterialFlags = pxFrame->m_uRemovedMaterialFlags;

	UpdateMaterialFlagListControl();

	//update strings indicating number of seqs/frames
	char szText[50];
	sprintf(szText,"(of %d)",pxAnim->m_uNumberOfSequences);
	s_xNumSequenceText = szText;
	sprintf(szText,"(of %d)",pxSeq->m_uNumberOfFrames);
	s_xNumFrameText = szText;
}

void Tools_TextureAnimation::UpdateAnimFromOverlayVariables()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return;

	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences || s_iSelectedSequence<0)
	{
		//sequence out of range... forget about this and update from the anim instead
		UpdateOverlayVariablesFromAnim();
		return;
	} 

	//update the sequence variables
	Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];
	pxSeq->m_fDuration = s_fSeqDuration;

	if(s_bSeqLoop && s_bSeqLoopBackAndForth)
	{
		//only one of these two should be set.. so go with the one that was just changed
		if(pxSeq->m_bLoop) s_bSeqLoop = false;
		else s_bSeqLoopBackAndForth = false;
	}
	pxSeq->m_bLoop = s_bSeqLoop;
	pxSeq->m_bLoopBackAndForth = s_bSeqLoopBackAndForth;
	//only allow one out of interpolate and random
	if(s_bSeqInterpolate && s_bSeqRandom)
	{
		if(!pxSeq->m_bInterpolate)
		{
			s_bSeqRandom = false;
		}
		else
		{
			s_bSeqInterpolate = false;
		}

	}
	pxSeq->m_bInterpolate = s_bSeqInterpolate;
	pxSeq->m_bRandom = s_bSeqRandom;

	if(s_iSelectedFrame >= (int) pxSeq->m_uNumberOfFrames || s_iSelectedFrame<0)
	{
		//frame out of range... forget about this and update from the anim instead
		UpdateOverlayVariablesFromAnim();
		return;
	}

	Asura_TextureAnimationFrame* pxFrame = &pxSeq->m_pxFrames[s_iSelectedFrame];
	if(s_bUseUVScale)
	{
		pxFrame->m_fU = s_fFrameU/s_fUVScale;
		pxFrame->m_fV = s_fFrameV/s_fUVScale;
	}
	else
	{
		pxFrame->m_fU = s_fFrameU;
		pxFrame->m_fV = s_fFrameV;
	}

	pxFrame->m_uAddedMaterialFlags = s_uFrameAddedMaterialFlags;
	pxFrame->m_uRemovedMaterialFlags = s_uFrameRemovedMaterialFlags;
	
	if(s_bFrameTimeInSecs)
	{
		if(s_bDurationChanged)
		{
			//If duration has changed we need to recalculate the display time in seconds
			s_fFrameTime = pxFrame->m_fTime*s_fSeqDuration;
		}
		else
		{
			s_fFrameTime = Asura_Maths::ClampToRange(s_fFrameTime,0.0f,s_fSeqDuration);
			pxFrame->m_fTime = s_fFrameTime/s_fSeqDuration;
		}
	}
	else
	{
		s_fFrameTime = Asura_Maths::ClampToRange(s_fFrameTime,0.0f,1.0f);
		pxFrame->m_fTime = s_fFrameTime;
	}
	
}


void Tools_TextureAnimation::BuildMaterialFlagList()
{
	DeleteMaterialFlagList();

	const Asura_TextureAnimationFrame* pxFrame = GetEditedFrame();

	u_int uCombinedFlags = pxFrame ? pxFrame->m_uAddedMaterialFlags|pxFrame->m_uRemovedMaterialFlags : 0;

	//determine the list size
	for(int i=0;i<32;i++)
	{
		if(uCombinedFlags & (1<<i))
		{
			s_iMaterialFlagListSize++;
		}
	}

	if(s_iMaterialFlagListSize)
	{
		s_pszMaterialFlagListStrings = new char*[s_iMaterialFlagListSize];
		int iIndex = 0;

		for(int i=0;i<32;i++)
		{
			int iFlag = 1<<i;
			if(uCombinedFlags & iFlag)
			{
				const char* szName = GetMaterialFlagName(iFlag);
				char* szListString = new char[strlen(szName)+2];

				szListString[0] = (pxFrame->m_uAddedMaterialFlags & iFlag) ? '+' : '-';
				strcpy(szListString+1,szName);

				s_pszMaterialFlagListStrings[iIndex++] = szListString;
			}
		}
	}
	else
	{
		//create list for no entries
		s_iMaterialFlagListSize = 1;
		s_pszMaterialFlagListStrings = new char*[1];

		s_pszMaterialFlagListStrings[0] = new char[10];
		strcpy(s_pszMaterialFlagListStrings[0], g_pszNoAnimString);

	}

	//make sure the selection index stays in range
	s_iSelectedMaterialFlag = Asura_Maths::ClampToRange(s_iSelectedMaterialFlag,0,s_iMaterialFlagListSize-1);

}

void Tools_TextureAnimation::UpdateMaterialFlagListControl()
{
	//get hold of the list control
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_EDITTEXTUREANIMATION);
	if(!pxOverlay) return;

	Asura_List_Dialog* pxDialog = (Asura_List_Dialog*) pxOverlay->GetDialog(Asura_GetHashID("MaterialFlagList"));
	if(!pxDialog) return;

	//and update
	BuildMaterialFlagList();
	pxDialog->ReplaceStrings(s_iMaterialFlagListSize,s_pszMaterialFlagListStrings);
}

void Tools_TextureAnimation::AddMaterialFlag()
{
	Asura_TextureAnimationFrame* pxFrame = GetEditedFrame();
	if(!pxFrame) return;

	//ask for flag to be added
	u_int uFlag = SelectMaterialFlag("Select flag to be added");

	//is this a new one?
	if(!uFlag) return;
	if(pxFrame->m_uAddedMaterialFlags & uFlag) return;

	//update the frame
	pxFrame->m_uAddedMaterialFlags |= uFlag;
	pxFrame->m_uRemovedMaterialFlags &=~ uFlag;

	//and update the list
	UpdateMaterialFlagListControl();

	SetModified();	
}

void Tools_TextureAnimation::RemoveMaterialFlag()
{
	Asura_TextureAnimationFrame* pxFrame = GetEditedFrame();
	if(!pxFrame) return;

	//ask for flag to be added
	u_int uFlag = SelectMaterialFlag("Select flag to be added");

	//is this a new one?
	if(!uFlag) return;
	if(pxFrame->m_uRemovedMaterialFlags & uFlag) return;

	//update the frame
	pxFrame->m_uRemovedMaterialFlags |= uFlag;
	pxFrame->m_uAddedMaterialFlags &=~ uFlag;

	//and update the list
	UpdateMaterialFlagListControl();

	SetModified();	
}

void Tools_TextureAnimation::DeleteMaterialFlag()
{
	Asura_TextureAnimationFrame* pxFrame = GetEditedFrame();
	if(!pxFrame) return;

	//determine the selected flag value
	u_int uCombinedFlags = pxFrame->m_uAddedMaterialFlags | pxFrame->m_uRemovedMaterialFlags;

	int iIndex=0;
	int uFlag=0;
	for(int i=0;i<32;i++)
	{
		if(uCombinedFlags & (1<<i))
		{
			if(iIndex++ == s_iSelectedMaterialFlag)
			{
				uFlag = 1<<i;	
				break;
			}
		}
	}
	if(!uFlag) return;

	pxFrame->m_uAddedMaterialFlags &=~ uFlag;	
	pxFrame->m_uRemovedMaterialFlags &=~ uFlag;	

	UpdateMaterialFlagListControl();
	SetModified();	
}

u_int Tools_TextureAnimation::SelectMaterialFlag(const char* szTitle)
{
	ListSelection_Overlay xOverlay(szTitle);

	for(int i=0;i<NO_OF_MATERIAL_FLAGS;i++)
	{
		xOverlay.AddItem(g_aszMaterialFlagNames[i],g_auMaterialFlagValues[i]);
	}

	if(!xOverlay.DoModal()) return 0;

	return (u_int) xOverlay.GetSelectionData();
}

Asura_TextureAnimationFrame* Tools_TextureAnimation::GetEditedFrame()
{
	Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(s_uEditedAnim);
	if(!pxAnim) return 0;

	if(s_iSelectedSequence >= (int)pxAnim->m_uNumberOfSequences || s_iSelectedSequence<0) return 0;
	Asura_TextureAnimationSequence* pxSeq = &pxAnim->m_pxSequences[s_iSelectedSequence];

	if(s_iSelectedFrame >= (int) pxSeq->m_uNumberOfFrames || s_iSelectedFrame<0) return 0;

	return &pxSeq->m_pxFrames[s_iSelectedFrame];

}

void Tools_TextureAnimation::DeleteMaterialFlagList()
{
	for(int i=0;i<s_iMaterialFlagListSize;i++)
	{
		delete [] s_pszMaterialFlagListStrings[i];
	}
	delete [] s_pszMaterialFlagListStrings;

	s_pszMaterialFlagListStrings = 0;
	s_iMaterialFlagListSize = 0;

}

const char* Tools_TextureAnimation::GetMaterialFlagName(u_int uValue)
{
	for(int i=0;i<NO_OF_MATERIAL_FLAGS;i++)
	{
		if(uValue == g_auMaterialFlagValues[i])
		{
			return g_aszMaterialFlagNames[i];
		}
	}
	return "*Unknown*";
}

void Tools_TextureAnimation::AddOverlayControls_TextureAnimationMessage(struct Asura_StaticMessage* pxMessage,class Asura_Overlay* pxOverlay,class Asura_Vector_2& xOffset)
{
	Asura_Assert(pxMessage->m_pData,"");
	Asura_Message_TextureAnimation* pxData = (Asura_Message_TextureAnimation*) pxMessage->m_pData;
	s_pxEditedMessage = pxData;

	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_FlagTickbox_Dialog("Change Sequence",xOffset,&pxData->m_uFlags,ASURA_MESSAGE_TEXTUREANIMATION_CHANGESEQUENCE);
	pxDialog->SetHelpText("Change the texture animation sequence used.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Sequence",xOffset,&pxData->m_uSequence,0,99,1,"%d");
	pxDialog->SetHelpText("Change the texture animation sequence used.");
	pxDialog->SetIsEnabledFunction(IsEnabled_Sequence);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Change Speed",xOffset,&pxData->m_uFlags,ASURA_MESSAGE_TEXTUREANIMATION_CHANGESPEED);
	pxDialog->SetHelpText("Change the speed multiplier for texture animation.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Speed",xOffset,&pxData->m_fSpeed,-999.0f,999.0f,0.1f,"%.2f");
	pxDialog->SetHelpText("Speed multiplier for texture animation.\n1 is normal speed.\n0 is paused.\nNegative values reverse the animation.");
	pxDialog->SetIsEnabledFunction(IsEnabled_Speed);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
}

bool Tools_TextureAnimation::IsEnabled_Sequence()
{
	Asura_Assert(s_pxEditedMessage,"");
	return s_pxEditedMessage->m_bChangeSequence;
}

bool Tools_TextureAnimation::IsEnabled_Speed()
{
	Asura_Assert(s_pxEditedMessage,"");
	return s_pxEditedMessage->m_bChangeSpeed;
}

void Tools_TextureAnimation::FindAnim()
{
	if(!IsAnimSelected()) return;

	const char* pszAnimName = s_ppszAnimNames[s_iSelectedAnim];
	Asura_Hash_ID uHash = Asura_GetHashID(pszAnimName);

	Tools_Texturing_Find::FindTextureAnim(uHash);
}