///////////////////////////////////////////////////////////////////////////////
//
// File: Tools_Container.cpp
//
// Created by Martin G.Harris (6/5/2008)
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Entity/Container/Tools_Container.h"

#ifdef ASURA_USE_CONTAINER_SYSTEM

// Asura
#include "Animation/Asura_Animation.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "Core/Asura_StringBuffer.h"
#include "Core/Asura_TextureManagement.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_Graph_Dialog.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_TexturePicker_Dialog.h"
#include "Physics/Asura_Physics_Object.h"

#include "Sound/Asura_Sound_Event.h"
#include "Sound/Asura_Sound_Event_Storage.h"

#include "SpecialFX/PFX/Asura_PFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

// Tools
#include "Entity/Container/Tools_Container_System.h"
#include "Entity/Container/Tools_ContainerFile.h"
#include "Entity/Container/Tools_ContainerRegistry.h"
#include "Entity/Container/Tools_Entity_Container.h"
#include "Entity/Container/Tools_Mode_Container.h"
#include "Entity/Tools_Entity_DynamicLight.h"
#include "Hierarchy/Tools_Animation_Files.h"
#include "Interface/Tools_ColourPicker.h"
#include "Misc/Tools_MessageLog.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Tools_System.h"

#include "Sound/Tools_PickSoundEvent_Overlay.h"

#include "Particles/Tools_Mode_PFX.h"
#include "Particles/Tools_PFX_Dialog.h"
#include "Particles/Tools_PFX_SourceVolume_Object.h"


//
// Register the Tools container types.
//
Tools_ContainerRegisterClass<Tools_Container_Mesh> xToolsMesh(AsuraContainerClass_Mesh, "Mesh");

Tools_ContainerRegisterClass<Tools_Container_SoundEvent> xToolsSE(AsuraContainerClass_SoundEvent, "SoundEvent");

Tools_ContainerRegisterClass<Tools_Container_PFX> xToolsPFX(AsuraContainerClass_PFX, "PFX");

Tools_ContainerRegisterClass<Tools_Container_Corona> xToolsCorona(AsuraContainerClass_Corona, "Corona");

#ifdef ASURA_TARGET_SM3
Tools_ContainerRegisterClass<Tools_Container_LightShaft> xToolsLightShaft(AsuraContainerClass_LightShaft, "LightShaft");
Tools_ContainerRegisterClass<Tools_Container_Projector>	xToolsProjector(AsuraContainerClass_Projector, "Projector");
Tools_ContainerRegisterClass<Tools_Container_Photon> xToolsPhoton(AsuraContainerClass_Photon, "Photon");
#endif //#ifdef ASURA_TARGET_SM3

Tools_ContainerRegisterClass<Tools_Container_Strobe> xToolsStrobe(AsuraContainerClass_Strobe, "Strobe", false);

Tools_ContainerRegisterClass<Tools_Container_GraphColour> xToolsColour(AsuraContainerClass_GraphColour, "GraphColour", false);

Tools_ContainerRegisterClass<Tools_Container_Health> xToolsHealth(AsuraContainerClass_Health, "Health", false);

Tools_ContainerRegisterClass<Tools_Container_Physics> xToolsPhysics(AsuraContainerClass_Physics, "Physics", false);

Tools_ContainerRegisterClass<Tools_Container_Blueprint> xToolsBlueprint(AsuraContainerClass_Blueprint, "Blueprint", false);

///////////////////////////////////////////////////////////////////////////////
// Tools_MessageLogAction_GotoContainer //
///////////////////////////////////////////////////////////////////////////////
// For associating message log entries with containers.
class Tools_MessageLogAction_GotoContainer : public Tools_MessageLogAction
{
public :
	Tools_MessageLogAction_GotoContainer(Asura_Hash_ID uContainerHash) : m_uContainerHash(uContainerHash) {}
	
	void Execute()
	{
		Tools_Container_System::SelectContainer(m_uContainerHash, true);
	}

private :
	Asura_Hash_ID m_uContainerHash;
};


///////////////////////////////////////////////////////////////////////////////
// Tools_Overlay_EditContainer 
///////////////////////////////////////////////////////////////////////////////
class Tools_Overlay_EditContainer : public Asura_Overlay
{
public:
	Tools_Overlay_EditContainer(const char* szName, bool bAlwaysOnTop, Asura_Vector_2* pxPosition)
		: Asura_Overlay(szName, bAlwaysOnTop, pxPosition)
		, m_pxContainer(0)
	{
	}

	~Tools_Overlay_EditContainer()
	{
		if (IsClosingByCloseGadget())
		{
			// Overlay was closed using the close gadget.
			Tools_Container_System::ClosingEditByCloseGadget();
		}
	}

	void SetContainer(Tools_Container* pxContainer)
	{
		m_pxContainer = pxContainer;
	}

	void PerFrameUpdate()
	{
		Asura_Overlay::PerFrameUpdate();
		if (m_pxContainer)
		{
			m_pxContainer->PerFrameOverlayUpdate();
		}
	}

private:
	Tools_Container* m_pxContainer;
};


///////////////////////////////////////////////////////////////////////////////
// Tools_Dialog_Container_FalloffGraph
///////////////////////////////////////////////////////////////////////////////
// Graph of fall off for projectors, dynamic lights etc.
#ifdef ASURA_TARGET_SM3
class Tools_Dialog_Container_FalloffGraph : public Asura_Dialog_FalloffGraph
{
public:
	Tools_Dialog_Container_FalloffGraph::Tools_Dialog_Container_FalloffGraph( const Asura_Vector_2& xOffset,
																			  float fWidth, float fHeight,
																			  const float& fFalloffInnerRatio,
																			  const float& fFalloffPower)
		: Asura_Dialog_FalloffGraph(xOffset, fWidth, fHeight)
		, m_fFalloffInnerRatio(fFalloffInnerRatio)
		, m_fFalloffPower(fFalloffPower)
	{
	}

protected:
	// Return true if the parameters were written to.
	virtual bool GetParameters(float& fInnerRatio, float& fPower) const
	{
		fInnerRatio = m_fFalloffInnerRatio;
		fPower		= m_fFalloffPower;
		return true;
	}
	
private:
	Asura_Vector_2 m_xSize;
	const float& m_fFalloffInnerRatio;
	const float& m_fFalloffPower;
};
#endif //#ifdef ASURA_TARGET_SM3

////////////////////////
// Asura_OverlayOrTab //
////////////////////////
// Helper to allow dialogues to be added to either overlays or tabs.
class Asura_OverlayOrTab
{
public:
	Asura_OverlayOrTab(Asura_Overlay* pxOverlay)
		: m_pxOverlay(pxOverlay), m_pxTabs(0)
	{}

	Asura_OverlayOrTab(Asura_Tabs_Dialog* pxTabs)
		: m_pxOverlay(0), m_pxTabs(pxTabs)
	{}

	void AddDialog(Asura_Dialog* pxNewDialog) const
	{
		if (m_pxOverlay) m_pxOverlay->AddDialog(pxNewDialog);
		if (m_pxTabs) m_pxTabs->AddDialog(pxNewDialog);
	}

private:
	Asura_Overlay* m_pxOverlay;
	Asura_Tabs_Dialog* m_pxTabs;
};


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_ChildIterator
///////////////////////////////////////////////////////////////////////////////
// Iterator for iterating over children in a Tools_Container.
// Should work for both attachments and collections, as well as
// any project specified containers that have children.
// The order of the children is arbitrary.

Tools_Container_ChildIterator::Tools_Container_ChildIterator(const Tools_Container* pxContainer)
: m_iTotalNumChildren(0)
, m_iMaxArraySize(0)
, m_iNumArrayEntries(0)
, m_puHash(0)
, m_piMultiplicity(0)
{
	Begin();

	if (pxContainer)
	{
		pxContainer->FillChildIterator(*this);
	}
}

Tools_Container_ChildIterator::~Tools_Container_ChildIterator()
{
	Reset();
}

// Free memory and reset state.
void Tools_Container_ChildIterator::Reset()
{
	delete [] m_puHash;
	m_puHash = 0;
	delete [] m_piMultiplicity;
	m_piMultiplicity = 0;

	m_iTotalNumChildren = 0;
	m_iMaxArraySize = 0;
	m_iNumArrayEntries = 0;

	Begin();
}

// Move to next entry.
void Tools_Container_ChildIterator::Next()
{
	if (m_iCurrentEntry>=0 && m_iCurrentEntry<m_iNumArrayEntries && m_piMultiplicity)
	{
		++m_iCurrentRepeat;
		if (m_iCurrentRepeat >= m_piMultiplicity[m_iCurrentEntry])
		{
			++m_iCurrentEntry;
			m_iCurrentRepeat = 0;
		}
	}
}

// Move to next entry, but skip if hash is same as previous value.
void Tools_Container_ChildIterator::NextSkipIfSame()
{
	const Asura_Hash_ID uLastHash = GetCurrentHash();
	while (!Done() && uLastHash == GetCurrentHash())
	{
		++m_iCurrentEntry;
		m_iCurrentRepeat = 0;
	}
}

// Return current hash
Asura_Hash_ID Tools_Container_ChildIterator::GetCurrentHash() const
{
	if (m_iCurrentEntry>=0 && m_iCurrentEntry<m_iNumArrayEntries && m_puHash)
	{
		return m_puHash[m_iCurrentEntry];
	}
	return ASURA_HASH_ID_UNSET;
}

// Return current container or NULL if not found.
Tools_Container* Tools_Container_ChildIterator::GetCurrent() const
{
	return Tools_Container_System::FindContainerByHash(GetCurrentHash());
}

// If there are no children then add an entry with ASURA_HASH_ID_UNSET.
void Tools_Container_ChildIterator::AddUnsetEntryIfNoChildren()
{
	if (m_iTotalNumChildren == 0)
	{
		SetArraySize(1);

		m_puHash[0] = ASURA_HASH_ID_UNSET;
		m_piMultiplicity[0] = 1;
		m_iTotalNumChildren = 1;
		m_iNumArrayEntries  = 1;
	}
}

// Set maximum array size. Clear out any old data.
void Tools_Container_ChildIterator::SetArraySize(int iMaxSize)
{
	Reset();

	m_iMaxArraySize = iMaxSize;

	if (m_iMaxArraySize > 0)
	{
		m_puHash = new Asura_Hash_ID[m_iMaxArraySize];
		m_piMultiplicity = new int[m_iMaxArraySize];
	}
}

// Append an entry to the array.
void Tools_Container_ChildIterator::AddEntry(Asura_Hash_ID uChildHash, int iMultiplicity /*= 1*/)
{
	// Ignore any unset hashes as they aren't really children.
	if (uChildHash == ASURA_HASH_ID_UNSET) return;
	
	Asura_Assert(m_iNumArrayEntries < m_iMaxArraySize, "Array full, can't store child hash.");
	if (m_iNumArrayEntries < m_iMaxArraySize)
	{
		Asura_Assert(m_puHash && m_piMultiplicity, "Array pointers shouldn't be null");
		m_puHash[m_iNumArrayEntries] = uChildHash;
		m_piMultiplicity[m_iNumArrayEntries] = iMultiplicity;
		
		++m_iNumArrayEntries;
		m_iTotalNumChildren += iMultiplicity;
	}
}


// Is the specified container a child?
bool Tools_Container_ChildIterator::IsMember(Asura_Hash_ID uContainerHash) const
{
	for (int i=0; i<m_iNumArrayEntries; ++i)
	{
		if (m_puHash[i] == uContainerHash)
		{
			return true;
		}
	}
	return false;
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container
///////////////////////////////////////////////////////////////////////////////

// Static data
bool Tools_Container::s_bChangedEditOverlay = false;
bool Tools_Container::s_bChangedEditOverlay_MinorChange = false;

Tools_Container::Tools_Container()
: m_uClassification(AsuraContainerClass_NotSet)
, m_uFileHash(ASURA_HASH_ID_UNSET)
, m_uGroupHash(ASURA_HASH_ID_UNSET)
, m_uContainerHash(ASURA_HASH_ID_UNSET)
, m_pxAsuraContainer(0)
, m_bReportedDepthError(false)
, m_uToolsFlags(0)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER = 1;

void Tools_Container::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container", iVersion));
		return;
	}

	xStream >> m_uFileHash;
	xStream >> m_uGroupHash;
	xStream >> m_xCreationContainerName;
	xStream >> m_xContainerName;

	if (iVersion >= 1)
	{
		xStream >> m_uToolsFlags;
	}
	else
	{
		m_uToolsFlags = 0;
	}

	m_uContainerHash = m_xCreationContainerName.GetHash();
}

void Tools_Container::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER;

	xStream << m_uFileHash;
	xStream << m_uGroupHash;
	xStream << m_xCreationContainerName;
	xStream << m_xContainerName;
	xStream << m_uToolsFlags;
}

///////////////////////////
// CopyDataFromContainer //
///////////////////////////
// Copy data from a source container of the same classification.
void Tools_Container::CopyDataFromContainer(const Tools_Container* pxSourceContainer)
{
	Asura_Assert(pxSourceContainer, "Null source container pointer");
	if (!pxSourceContainer) return;

	if (this == pxSourceContainer) return;

	if (GetClassification() != pxSourceContainer->GetClassification())
	{
		Asura_Assert(0, "Can't copy data. Wrong container classification.");
		return;
	}
	
	// Save data that we mustn't overwrite.
	// Note: currently we don't need to save any of the Tools flags.
	const Asura_Hash_ID uFileHash				= m_uFileHash;
	const Asura_Hash_ID uGroupHash				= m_uGroupHash;
	const Asura_String	xCreationContainerName  = m_xCreationContainerName;
	const Asura_Hash_ID uContainerHash			= m_uContainerHash;
	const Asura_String	xContainerName			= m_xContainerName;


	// Max size for storing chunk. Generally container chunks are very small.
	const u_int uSize = 32768;

	// Serialise the source container.
	Asura_Chunk_Memory_Stream xMemoryStream(uSize);
	xMemoryStream.SetSwapEndian(false);
	pxSourceContainer->WriteToChunkStream(xMemoryStream);

	// Copy serialised data to the destination (this).
	xMemoryStream.SetPosition(0);
	ReadFromChunkStream(xMemoryStream);

	// Restore the data that we didn't want to modify.
	m_uFileHash				 = uFileHash;
	m_uGroupHash			 = uGroupHash;
	m_xCreationContainerName = xCreationContainerName;
	m_uContainerHash		 = uContainerHash;
	m_xContainerName		 = xContainerName;

	// Refresh anything that might need updating.
	PostLoadInitialise();
}

////////////////////
// GetDescription //
////////////////////
const char* Tools_Container::GetDescription() const
{
	return Tools_ContainerRegistry::GetContainerClassName(m_uClassification);
}

///////////////////
// CreateOverlay //
///////////////////
// Create overlay for setting properties.
void Tools_Container::CreateOverlay(bool bSortToFront)
{
	Asura_String256 xBuffer("%s: %s", GetDescription(), m_xContainerName.GetString());

	const Tools_ContainerFile* const pxFile = Tools_Container_System::FindFileByHash(GetFileHash());
	if (pxFile && !pxFile->IsWriteable())
	{
		xBuffer += "  [read only]";
	}

	if (!IsGroupScope())
	{
		xBuffer += "  [global]";
	}

	Tools_Overlay_EditContainer* pxNewOverlay = new Tools_Overlay_EditContainer( xBuffer.GetString(), false, & Tools_Container_System::GetEditPosition() );
	if ( !pxNewOverlay ) return;

	Asura_Vector_2 xOffset = Asura_Vector_2( 1.0f, 3.0f );
	pxNewOverlay->m_uID = GetOverlayID();
	pxNewOverlay->m_bSortToFront = bSortToFront;
	pxNewOverlay->SetContainer(this);

	AddOverlayControls(*pxNewOverlay, xOffset);

	Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay, true );
}

////////////////////
// DestroyOverlay //
////////////////////
void Tools_Container::DestroyOverlay()
{
	Asura_Overlay_System::CloseOverlays( GetOverlayID() );
}

///////////////////
// ResizeOverlay //
///////////////////
// Resize the overlay if the controls have changed size.
void Tools_Container::ResizeOverlay()
{
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetOverlayID() );
	if (pxOverlay)
	{
		pxOverlay->ResizeOverlay();
	}
}

/////////////////
// SetModified //
/////////////////
// Flag this container as having been modified.
void Tools_Container::SetModified(bool bMinorChange /*= false*/)
{
	if (GetContainerHash() == Tools_Container_System::GetOverlayManager().GetEditingContainerHash())
	{
		if (s_bChangedEditOverlay || s_bChangedEditOverlay_MinorChange)
		{
			CopyDataToAsura();
		}
		s_bChangedEditOverlay = false;

		if (s_bChangedEditOverlay_MinorChange)
		{
			s_bChangedEditOverlay_MinorChange = false;
			bMinorChange = true;
		}
	}

	Tools_Container_System::SetFileModified(m_uFileHash);
	
	// If not a minor change then we need to rebuild the instance from scratch.
	Tools_Container_System::SetContainerModified(GetContainerHash(), bMinorChange);
}

//////////////////////////////
// SetCreationContainerName //
//////////////////////////////
// Set the intial creation name from the filename, group and container names.
// This should be unique even if a number of people are working on the container files
// at the same time.
void Tools_Container::SetCreationContainerName()
{
	const Tools_ContainerGroup* pxGroup = Tools_Container_System::FindGroupByHash(m_uGroupHash);
	MakeCreationName(m_xCreationContainerName, m_xContainerName, pxGroup);

	m_uContainerHash = m_xCreationContainerName.GetHash();
}

//////////////////////
// MakeCreationName //
//////////////////////
void Tools_Container::MakeCreationName(Asura_String& xName, const Asura_String& xContainerName, const Tools_ContainerGroup* pxGroup)
{
	Asura_Assert(!xContainerName.IsBlank(), "Blank container name!");
	Asura_Assert(pxGroup, "Null container group");

	Asura_String2048 xBuffer;
	if (pxGroup)
	{
		Asura_Assert(!pxGroup->GetCreationGroupName().IsBlank(), "Blank container group name!");
		xBuffer += pxGroup->GetCreationGroupName().GetString();
		xBuffer += "\\";
	}
	
	xBuffer += xContainerName.GetString();

	xName = xBuffer.GetString();
}

/////////////////////
// MakeCurrentName //
/////////////////////
// Make a user readable full name e.g. "file\group\container".
void Tools_Container::MakeFullName(Asura_String& xOutputName) const
{
	Asura_String2048 xBuffer;

	const Tools_ContainerFile* pxFile = Tools_Container_System::FindFileByHash(m_uFileHash);
	if (pxFile)
	{
		Asura_Assert(!pxFile->GetShortName().IsBlank(), "Blank container short filename!");
		xBuffer += pxFile->GetShortName().GetString();
		xBuffer += "\\";
	}

	const Tools_ContainerGroup* pxGroup = Tools_Container_System::FindGroupByHash(m_uGroupHash);
	if (pxGroup)
	{
		Asura_Assert(!pxGroup->GetGroupName().IsBlank(), "Blank container group name!");
		xBuffer += pxGroup->GetGroupName().GetString();
		xBuffer += "\\";
	}
	
	xBuffer += m_xContainerName.GetString();

	xOutputName = xBuffer.GetString();
}

///////////////////
// AddLogMessage //
///////////////////
// Log an error message, increment error count.
void Tools_Container::AddLogMessage(const char* szFormat,...) const
{
	if (!szFormat) return;

	Asura_String xFullName;
	MakeFullName(xFullName);

	Asura_String2048 xBuffer;
	xBuffer += "Container '";
	xBuffer += xFullName.GetString();
	xBuffer += "': ";

	char szBuffer[512];

	va_list xArgs;
	va_start(xArgs, szFormat);
	Asura_PrintfToBfr(szBuffer, szFormat, xArgs, sizeof(szBuffer), true);
	va_end(xArgs);

	xBuffer += &szBuffer[0];

	Tools_MessageLog::AddMessage( Tools_Container_System::GetCategory(), Tools_Container_System::GetSubCategory(),
								  new Tools_MessageLogAction_GotoContainer(GetContainerHash()), xBuffer.GetString());

	Tools_Container_System::IncrementErrorCount();
}

/////////////////////
// AddInstanceName //
/////////////////////
void Tools_Container::AddInstanceName(Asura_String256& xBuffer, bool bAddContainerName, bool bAddDescription) const
{
	if (bAddDescription)
	{
		xBuffer += GetDescription();
	}

	if (bAddContainerName)
	{
		if (bAddDescription) xBuffer += " ";
		xBuffer += GetContainerName().GetString();
	}

	const char* const szInstanceName = GetInstanceName();
	if (szInstanceName && *szInstanceName)
	{
		xBuffer += " (";
		xBuffer += szInstanceName;
		xBuffer += ")";
	}

	if (ShouldShowName() && bAddDescription && !bAddContainerName)
	{
		xBuffer += " (";
		xBuffer += GetContainerName().GetString();
		xBuffer += ")";
	}
}

////////////////////
// CheckIsTooDeep //
////////////////////
// Check for excessive depth of tree. If too deep then return true, and display message first time this happens.
bool Tools_Container::CheckIsTooDeep(bool bClearError /*= false*/) const
{
	if (bClearError)
	{
		m_bReportedDepthError = false; // Force retest.
	}

	if (m_bReportedDepthError)
	{
		return true;
	}

	const int iDepth = GetDepth(iASURA_MAX_CONTAINER_DEPTH);

	const bool bIsTooDeep = (iDepth >= iASURA_MAX_CONTAINER_DEPTH);

	if (bIsTooDeep)
	{
		m_bReportedDepthError = true;

		Asura_String xTemp;
		MakeFullName(xTemp);
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Container '%s' has circular reference.", xTemp.GetString());
	}
	
	return m_bReportedDepthError;
}

///////////////////////
// PromptForDeletion //
///////////////////////
// Prompt user for deletion/removal. Return true if Ok, false if cancelled.
bool Tools_Container::PromptForDeletion(const bool bRemoval) const
{
	// Ask the user.
	const char* const szStringOp = (bRemoval ? "Remove" : "Delete");
	const Asura_String256 xMessage("%s container '%s' ?", szStringOp, GetContainerName().GetString());

	YesNo_Overlay xPrompt(xMessage.GetString());
	return xPrompt.DoModal();
}

///////////////////////
// RemoveAllChildren //
///////////////////////
// Remove all children.
void Tools_Container::RemoveAllChildren()
{
	Tools_Mode_Container::MarkDeletionStart();

	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.Next() )
	{
		const Asura_Hash_ID uHash = xIt.GetCurrentHash();
		if (!RemoveChild(uHash))
		{
			// Failed to remove child.
			Asura_Assert(0, "Couldn't remove all the children from container");
		}
	}

	Tools_Mode_Container::MarkDeletionStop();
}


//////////////
// Contains //
//////////////
// Recursive check to see whether this contains (or equals) the specified container.
bool Tools_Container::Contains(Asura_Hash_ID uSearchHash) const
{
	return ContainsHelper(uSearchHash, 0);
}

////////////////////
// ContainsHelper //
////////////////////
// Recursive check to see whether this contains (or equals) the specified container.
bool Tools_Container::ContainsHelper(Asura_Hash_ID uSearchHash, int iDepth) const
{
	if (uSearchHash == ASURA_HASH_ID_UNSET)
	{
		return false;
	}

	if (uSearchHash == GetContainerHash())
	{
		return true;
	}

	if (iDepth >= iASURA_MAX_CONTAINER_DEPTH)
	{
		// Circular reference, don't go any deeper.
		return false;
	}

	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{	
		const Asura_Hash_ID uHash = xIt.GetCurrentHash();
		if (uHash == uSearchHash)
		{
			return true;
		}

		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer && pxContainer->ContainsHelper(uSearchHash, iDepth+1))
		{
			return true;
		}
	}

	return false;
}


//////////////
// GetDepth //
//////////////
// Determine the depth of the tree, stopping at max value. Used to check for circular references.
int Tools_Container::GetDepth(const int iMax) const
{
	if (iMax <= 1)
	{
		return iMax;
	}
	
	int iMaxDepth = 0;

	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{
		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			const int iDepth = pxContainer->GetDepth(iMax - 1);
			if (iDepth > iMaxDepth)
			{
				iMaxDepth = iDepth;
			}
		}
	}

	return iMaxDepth + 1;
}

///////////////////////////////////
// TraverseUsedTexturesForReport //
///////////////////////////////////
// Build a list of textures for texture report log.
void Tools_Container::TraverseUsedTexturesForReport(void (*pfnCallback)(int iTextureID,void* pUserData), void* pUserData) const
{
	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{	
		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			pxContainer->TraverseUsedTexturesForReport(pfnCallback, pUserData);
		}
	}
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
// Add files for any children.
void Tools_Container::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{	
		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			pxContainer->AddFilesToExportList(xNameList);
		}
	}
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
// Load files for any children.
void Tools_Container::LoadRequiredFiles() const
{
	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{	
		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			pxContainer->LoadRequiredFiles();
		}
	}
}

////////////////////////////
// AddTextureToExportList //
////////////////////////////
// Helper. Find texture and add it to the export list.
void Tools_Container::AddTextureToExportList(Asura_Hash_ID uTextureHash, const Asura_String& xTextureName) const
{
	Asura_Assert(uTextureHash == Asura_GetHashID_IgnoreExtension(xTextureName.GetString()), "Texture hash and name don't match");

	if (uTextureHash != ASURA_HASH_ID_UNSET)
	{
		const int iTex = Asura_Texture_Management::GetIndexFromHashID(uTextureHash);
		if (iTex != ASURA_TEXTURE_NONE)
		{	
			Tools_Level_Export::AddTextureToExportList(iTex);
		}
		else
		{
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "%s texture '%s' not found.", GetDescription(), xTextureName.GetString());
		}
	}
}

/////////////////////////////
// LoadRequiredTextureFile //
/////////////////////////////
// Helper. Load required texture file.
void Tools_Container::LoadRequiredTextureFile(Asura_Hash_ID uTextureHash, const Asura_String& xTextureName) const
{
	Asura_Assert(uTextureHash == Asura_GetHashID_IgnoreExtension(xTextureName.GetString()), "Texture hash and name don't match");

	if ( uTextureHash != ASURA_HASH_ID_UNSET &&
		!xTextureName.IsBlank() )
	{
		const int iTex = Asura_Texture_Management::GetIndexFromHashID(uTextureHash);
		if (iTex == ASURA_TEXTURE_NONE)
		{
			Asura_Texture_Management::AddTexture(xTextureName);
			Asura_Texture_Management::LoadTextures();
		}
	}
}

///////////////////
// VerifyTexture //
///////////////////
// Helper. Verify texture.
void Tools_Container::VerifyTexture(Asura_Hash_ID uTextureHash, const Asura_String& xTextureName, const char* szDescription, bool bMustBeSet /*= true*/) const
{
	if (!szDescription) szDescription = "";

	if (uTextureHash == ASURA_HASH_ID_UNSET)
	{
		if (bMustBeSet)
		{
			AddLogMessage("%s texture not set.", szDescription);
		}
	}
	else if (xTextureName.IsBlank())
	{
		AddLogMessage("%s texture name is blank.", szDescription);
	}
	else if (ASURA_TEXTURE_NONE == Asura_Texture_Management::GetIndexFromHashID(uTextureHash))
	{
		AddLogMessage("%s texture '%s' not found.", szDescription, xTextureName.GetString());
	}
}

////////////
// Verify //
////////////
void Tools_Container::Verify(bool bRecurse) const
{
	// Clear and retest the error, as it may have been fixed.
	if (CheckIsTooDeep(true))
	{
		AddLogMessage("has circular reference.");
	}

	// Check that the children exist.
	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{
		const Tools_Container* pxContainer = xIt.GetCurrent();
		if (!pxContainer)
		{
			const Asura_Hash_ID uHash = xIt.GetCurrentHash();
			AddLogMessage("container (0x%08x) not found.", uHash);
		}
		else if (bRecurse && !HasReportedDepthError())
		{
			pxContainer->Verify(bRecurse);
		}
	}

	// Check that the group and file exist and that cached data is consistent.
	// Note: None of these errors should happen unless there is a code bug. They aren't easily fixable by the user.
	// If necessary we could write some code to fix up these problems automatically.
	const Tools_ContainerGroup* const pxGroup = Tools_Container_System::FindGroupByHash(GetGroupHash());
	if (!pxGroup)
	{
		AddLogMessage("internal error: container group (0x%08x) not found.", GetGroupHash());
	}
	else
	{
		if (GetFileHash() != pxGroup->GetFileHash())
		{
			AddLogMessage("internal error: container file (0x%08x) is inconsistent with group's file (0x%08x).", GetFileHash(), pxGroup->GetFileHash());
		}
	}
	const Tools_ContainerFile* const pxFile = Tools_Container_System::FindFileByHash(GetFileHash());
	if (!pxFile)
	{
		AddLogMessage("internal error: container file (0x%08x) not found.", GetFileHash());
	}

	if (Tools_Container_System::IsOrphaned(this))
	{
		if (IsGroupScope())
		{
			AddLogMessage("container is orphaned. It should be deleted.");
		}
		else
		{
			AddLogMessage("container is currently unused. If it is no longer needed then please delete it.");
		}
	}
}

/////////////////
// FixUpErrors //
/////////////////
// Fix up certain types of error, automatically.
void Tools_Container::FixUpErrors()
{
	if (CheckIsTooDeep()) return;

	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{
		Tools_Container* pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			pxContainer->FixUpErrors();
		}
	}
}

/////////////////////
// AddMarkerPoints //
/////////////////////
// Add any marker point names to the given list.
void Tools_Container::AddMarkerPoints(ListSelection_Overlay& xList, const Asura_Hash_ID uMarkerHash) const
{
	for ( Tools_Container_ChildIterator xIt(this); !xIt.Done(); xIt.NextSkipIfSame() )
	{	
		const Tools_Container* const pxContainer = xIt.GetCurrent();
		if (pxContainer)
		{
			pxContainer->AddMarkerPoints(xList, uMarkerHash);
		}
	}
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Mesh
///////////////////////////////////////////////////////////////////////////////

static const char* const g_aszCollisionProxyName[Asura_Physics_Object::NUM_COLLISION_PROXIES] =
{
	"OBB",
	"Sphere",
	"Sphere-Stack",
	"Polygonal",
};

Tools_Container_Mesh::Tools_Container_Mesh()
	: m_iAnimFlags(0)
	, m_uRenderFlags(ASURA_HIERARCHY_RENDER_NO_SHADOW)
	, m_uContainerMeshFlags(ASURA_CONTAINER_MESH_FLAG_BLOCK_NAVDATA_GENERATION)
	, m_iCollisionProxy(Asura_Physics_Object::COLLISION_PROXY_OBB)
	, m_fSelfIlluminationContribution(1.0f)
	, m_fSelfIlluminationColourise(1.0f)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_MESH = 2;

void Tools_Container_Mesh::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_MESH)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Mesh", iVersion));
		return;
	}
	
	xStream >> m_xSkinName;
	xStream >> m_xSkinFilename;
	xStream >> m_xAnimName;
	xStream >> m_xAnimFilename;

	xStream >> m_iAnimFlags;
	xStream >> m_uRenderFlags;
	xStream >> m_uContainerMeshFlags;
	xStream >> m_iCollisionProxy;

	if (iVersion >= 1)
	{
		xStream >> m_fSelfIlluminationContribution;
		xStream >> m_fSelfIlluminationColourise;
	}

	if (iVersion < 2)
	{
		// Old versions didn't have a "block navdata generation" flag,
		// when loading these set the flag by default.
		m_uContainerMeshFlags |= ASURA_CONTAINER_MESH_FLAG_BLOCK_NAVDATA_GENERATION;
	}

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Mesh::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_MESH;

	xStream << m_xSkinName;
	xStream << m_xSkinFilename;
	xStream << m_xAnimName;
	xStream << m_xAnimFilename;

	xStream << m_iAnimFlags;
	xStream << m_uRenderFlags;
	xStream << m_uContainerMeshFlags;
	xStream << m_iCollisionProxy;

	xStream << m_fSelfIlluminationContribution;
	xStream << m_fSelfIlluminationColourise;

	PARENT::WriteToChunkStream(xStream);
}


/////////////////////
// RefreshDialogue //
/////////////////////
void Tools_Container_Mesh::RefreshDialogue()
{
	m_xSkinNameDialogue = "Skin: ";
	m_xSkinNameDialogue += m_xSkinFilename;
	m_xAnimNameDialogue = "Anim: ";
	m_xAnimNameDialogue += m_xAnimFilename;
}

// Helper functions
static bool IsCollisionEnabled(const u_int* puMeshFlags)
{
	const u_int uMeshFlags = (puMeshFlags ? *puMeshFlags : 0);
	return (uMeshFlags & ASURA_CONTAINER_MESH_FLAG_NO_COLLISIONS) == 0;
}

static bool IsMeshPhysicsEnabled(const u_int* puMeshFlags)
{
	const u_int uMeshFlags = (puMeshFlags ? *puMeshFlags : 0);
	return	((uMeshFlags & ASURA_CONTAINER_MESH_FLAG_NO_COLLISIONS)  == 0) &&
			((uMeshFlags & ASURA_CONTAINER_MESH_FLAG_PHYSICS_OBJECT) != 0);
}

static bool IsSelfIlluminationEnabled(const u_int* puMeshFlags)
{
	const u_int uMeshFlags = (puMeshFlags ? *puMeshFlags : 0);
	return 0 != (uMeshFlags & ( ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_COLOUR  |
								ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_FLICKER |
							    ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_STROBE ) );
}

static bool IsSelfIlluminationColourEnabled(const u_int* puMeshFlags)
{
	const u_int uMeshFlags = (puMeshFlags ? *puMeshFlags : 0);
	return 0 != (uMeshFlags & ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_COLOUR);
}

////////////////////////
// AddOverlayControls //
////////////////////////
// Add the overlay controls for selecting skin and animation.
void Tools_Container_Mesh::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	if (!m_pxAsuraContainer) return;

	// Refresh the dynamic string labels.
	RefreshDialogue();

	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_Dynamic_Dialog(&m_xSkinNameDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Dynamic_Dialog(&m_xAnimNameDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Tabs dialogue
	Asura_Tabs_Dialog* pxTabsDialog = new Asura_Tabs_Dialog(xOffset, 0, 0);
	xNewOverlay.AddDialog(pxTabsDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	xNewOverlay.SetCurrentParentDialog(pxTabsDialog);

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

	// Skin and anim tab
	pxTabsDialog->AddTab("Skin & Anim");

	pxDialog = new Asura_Function_Dialog("Select Skin and Animation", xOffset, SelectSkinAndAnim, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Select a skin and optionally an animation.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Select Animation", xOffset, SelectAnim, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Select an animation.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_StringDisplay_Dialog("Animation flags:", xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Looping", xOffset, &m_iAnimFlags, AsuraAnimationFlag_Loop, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Reversed", xOffset, &m_iAnimFlags, AsuraAnimationFlag_Reversed, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Use Anim Movement", xOffset, &m_iAnimFlags, AsuraAnimationFlag_UseMovementOffset, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Randomise timer", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_ANIMTIMER_RANDOMISE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Randomise the initial animation timer. Use this to desynchronise different instances of the same container.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Preserve timer when changing state", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_ANIMTIMER_PRESERVE, &s_bChangedEditOverlay_MinorChange);
	pxDialog->SetHelpText("Try to preserve the animation timer when a state change swaps two meshes that use the same animation. E.g. when an undamaged mesh is replaced by the damaged version. A maximum of 2 such timers can be stored per collection.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

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

	// Physics tab
	pxTabsDialog->AddTab("Physics");
	xOffset.y = fTabsStartY;

	pxDialog = new Asura_FlagTickbox_Dialog("No collisions", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_NO_COLLISIONS, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Disable physics collisions.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	Asura_Callback_UserData<bool, const u_int*> xIsCollisionEnabled(IsCollisionEnabled, &m_uContainerMeshFlags);

	pxDialog = new Asura_FlagTickbox_Dialog("Physics object", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_PHYSICS_OBJECT, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Tick to enable dynamic physics, otherwise it will be static.");
	pxDialog->SetIsEnabledFunction(xIsCollisionEnabled);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	Asura_Callback_UserData<bool, const u_int*> xIsMeshPhysicsEnabled(IsMeshPhysicsEnabled, &m_uContainerMeshFlags);

	pxDialog = new Asura_FlagTickbox_Dialog("Don't share physics object with collection", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_HAS_OWN_PHYSICS, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Tick to make the mesh use its own separate physics position and orientation rather than sharing them with other meshes in the collection. Typically different damaged versions of a mesh need to share.");
	pxDialog->SetIsEnabledFunction(xIsMeshPhysicsEnabled);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_DropDown_List_Dialog( xOffset, g_aszCollisionProxyName, &m_iCollisionProxy, 5, Asura_Physics_Object::NUM_COLLISION_PROXIES, NULL, &s_bChangedEditOverlay, "Collision Proxy" );
	pxDialog->SetHelpText( "Defines the shape of mesh used by this object for collisions." );
	pxDialog->SetIsEnabledFunction(xIsCollisionEnabled);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

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

	// Self-illumination
	pxTabsDialog->AddTab("Self-illumination");
	xOffset.y = fTabsStartY;

	pxDialog = new Asura_FlagTickbox_Dialog("Use Strobe for self-illumination", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_STROBE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the self-illumination will be affected by any active Strobe in the same collection.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Colour for self-illumination", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_COLOUR, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the self-illumination will be affected by any active GraphColour, in the same collection, that has 'Colour' enabled.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Flicker for self-illumination", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_USE_SELF_ILLUMINATION_FLICKER, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the self-illumination will be affected by any active GraphColour, in the same collection, that has 'Flicker' enabled.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Contribution to self-illumination	
	Asura_Callback_UserData<bool, const u_int*> xIsSelfIlluminationEnabled(IsSelfIlluminationEnabled, &m_uContainerMeshFlags);

	pxDialog = new Asura_ScaledNumerical_Dialog( "% Self-Illumination", xOffset, 100.0f, &m_fSelfIlluminationContribution,
										   0.0f, 10000.0f, 1.0f, "%.0f", &s_bChangedEditOverlay );
	pxDialog->SetHelpText( "Sets the amount of self-illumination to add to the overall image, in percent." );
	pxDialog->SetIsEnabledFunction( xIsSelfIlluminationEnabled );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	// Colourise self-illumination	
	Asura_Callback_UserData<bool, const u_int*> xIsSelfIlluminationColourEnabled(IsSelfIlluminationColourEnabled, &m_uContainerMeshFlags);

	pxDialog = new Asura_ScaledNumerical_Dialog( "% Colourise Self-Illumination", xOffset, 100.0f, &m_fSelfIlluminationColourise,
										   0.0f, 100.0f, 1.0f, "%.0f", &s_bChangedEditOverlay );
	pxDialog->SetHelpText( "Sets the percentage to use of the self-illumination colour, which comes from the GraphColour." );
	pxDialog->SetIsEnabledFunction( xIsSelfIlluminationColourEnabled );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

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

	// Misc tab
	pxTabsDialog->AddTab("Misc");
	xOffset.y = fTabsStartY;

	pxDialog = new Asura_FlagTickbox_Dialog("No shadow", xOffset, &m_uRenderFlags, ASURA_HIERARCHY_RENDER_NO_SHADOW, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the shadow is not rendered.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Don't render mesh", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_DONT_RENDER, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the skin is not rendered, although bones are still updated and attachments are rendered.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Ignore for LOS", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_IGNORE_FOR_LOS, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the skin is ignored by line-of-sight tests.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Ignore for damage", xOffset, &m_uContainerMeshFlags, ASURA_CONTAINER_MESH_FLAG_IGNORE_FOR_DAMAGE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then gunfire, explosions etc will do no damage when they hit this mesh. (This is only relevant if the parent collection has a health container.)");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Mark waypoint links as blocked", xOffset, &m_uContainerMeshFlags,
											ASURA_CONTAINER_MESH_FLAG_BLOCK_WAYPOINT_LINKS,
											&s_bChangedEditOverlay_MinorChange );
    pxDialog->SetHelpText( "Whether the object should mark any waypoint links going through it as blocked. Only has an effect if collisions are enabled." );
    xNewOverlay.AddDialog( pxDialog );
    xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Block nav data generation", xOffset, &m_uContainerMeshFlags,
											ASURA_CONTAINER_MESH_FLAG_BLOCK_NAVDATA_GENERATION,
											&s_bChangedEditOverlay_MinorChange);
	pxDialog->SetHelpText( "Whether the object should block waypoint and link generation. Only has an effect if collisions are enabled and the object is static." );
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

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

	// End of tabs
	xOffset.y = fTabsEndY + 1.0f;
	xNewOverlay.SetCurrentParentDialog(0);
	pxTabsDialog->SelectTab(0);

	// Remember which tab is open, when switching between different containers.
	static u_int s_uSelectedTab = 0;
	pxTabsDialog->SetSelectedTabPointer(&s_uSelectedTab);
}

///////////////////////
// SelectSkinAndAnim //
///////////////////////
void Tools_Container_Mesh::SelectSkinAndAnim()
{
	if (!Tools_Animation_Files::ChooseSkin(&m_xSkinName, &m_xSkinFilename))
	{
		return;
	}
	
	Tools_Animation_Files::ChooseAnimation(m_xSkinName.GetString(), NULL, &m_xAnimName, &m_xAnimFilename, 0, true);

	// Update the Asura container.
	CopyDataToAsura();

	// Refresh the dynamic string labels.
	RefreshDialogue();
	ResizeOverlay();

	// Attempt to get the textures in for this skin.
	Asura_Texture_Management::LoadTextures();

	// Mark the relevant group as modified.
	SetModified();
}

////////////////
// SelectAnim //
////////////////
void Tools_Container_Mesh::SelectAnim()
{
	// Look up the current animation.
	Asura_Hierarchy_Anim* pxInitialAnim = 0;
	if (!m_xAnimName.IsBlank())
	{
		pxInitialAnim = Asura_Animation_System::GetAnim(m_xAnimName.GetString());
	}

	
	bool bWasCancelled = false;
	Asura_Hierarchy_Anim* pxNewAnim = Tools_Animation_Files::ChooseAnimation(m_xSkinName.GetString(), NULL, &m_xAnimName, &m_xAnimFilename, pxInitialAnim, true, &bWasCancelled);
	if ( (pxNewAnim == pxInitialAnim) &&
		 (pxInitialAnim != 0 || bWasCancelled) )
	{
		// If user cancels and we get back the original animation, then the anim name and filename won't have been set, 
		// so return here instead.
		return;
	}

	if (pxNewAnim == 0)
	{
		// User selected "*None*" as anim.
		m_xAnimName.MakeNull();
		m_xAnimFilename.MakeNull();
	}

	// Update the Asura container.
	CopyDataToAsura();

	// Refresh the dynamic string labels.
	RefreshDialogue();
	ResizeOverlay();

	// Mark the relevant group as modified.
	SetModified();
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Mesh::CopyDataToAsura()
{
	Asura_Container_Mesh* pxContainerMesh = static_cast<Asura_Container_Mesh*>(m_pxAsuraContainer);
	Asura_Assert(pxContainerMesh, "Null pointer, container not set.");
	if (pxContainerMesh)
	{
		pxContainerMesh->m_uSkinHash      = m_xSkinName.GetHash();
		pxContainerMesh->m_uAnimationHash = m_xAnimName.GetHash();

		pxContainerMesh->m_iAnimFlags	= m_iAnimFlags;
		pxContainerMesh->m_uRenderFlags = m_uRenderFlags;
		pxContainerMesh->m_uContainerMeshFlags = m_uContainerMeshFlags;

		pxContainerMesh->SetCollisionProxy(m_iCollisionProxy);

		pxContainerMesh->m_fSelfIlluminationContribution = m_fSelfIlluminationContribution;
		pxContainerMesh->m_fSelfIlluminationColourise	 = m_fSelfIlluminationColourise;
	}

	PARENT::CopyDataToAsura();
}

////////////
// Verify //
////////////
void Tools_Container_Mesh::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that the skin and anim is loaded.
	LoadRequiredFiles();

	const Asura_Hash_ID uSkinHash = m_xSkinName.GetHash();
	if (uSkinHash == ASURA_HASH_ID_UNSET)
	{
		AddLogMessage("skin not set.");
	}
	else if (Asura_Animation_System::GetSkinByHash(uSkinHash) == 0)
	{
		AddLogMessage("skin '%s' not found.", m_xSkinName.GetString() );
	}

	const Asura_Hash_ID uAnimID = m_xAnimName.GetHash();
	if (uAnimID != ASURA_HASH_ID_UNSET)
	{
		const Asura_Hierarchy_Anim* pxAnim = Asura_Animation_System::GetAnimByHash(uAnimID);
		if (!pxAnim)
		{
			AddLogMessage("anim '%s' not found.", m_xAnimName.GetString() );
		}
	}
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_Mesh::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	// MGH 23/5/2008: Export code adapted from that in Tools_Entity_PhysicalObject. Comment below is copied from there.
	// RAR 25/4/2007: If there are platform specific versions of the file, then do the lookup based on the skin ID to make
	// sure the appropriate one for the platform is found.
	// It might be better to always ignore the file ID, but there could be problems with existing projects
	// if they have a file appearing in more than one place in the directories being searched.
	const Asura_Hash_ID uSkinFileID = m_xSkinFilename.GetHash();
	if (uSkinFileID != ASURA_HASH_ID_UNSET)
	{
		const char* szSkinFileName = 0;
		const Asura_Hash_ID uSkinID = m_xSkinName.GetHash();
		if (Asura_HashedFilename_System::DoesStrippedFilenameHavePlatformVersions(uSkinID))
		{
			szSkinFileName = Asura_HashedFilename_System::GetFullFilename(uSkinID, false);
			Asura_Assertf(szSkinFileName, ("Failed to determine platform version for skin '%s'", m_xSkinFilename.GetString()));
		}

		if (szSkinFileName)
		{
			Tools_Level_Export::AddFileToExportList(xNameList, Asura_String(szSkinFileName), true);
		}
		else
		{
			Tools_Level_Export::AddFileToExportList(xNameList, m_xSkinFilename, true);
		}
	}

	// Repeat for anims.
	const Asura_Hash_ID uAnimFileID = m_xAnimFilename.GetHash();
	if (uAnimFileID != ASURA_HASH_ID_UNSET)
	{
		const char* szAnimFileName = 0;
		const Asura_Hash_ID uAnimID = m_xAnimName.GetHash();
		if (Asura_HashedFilename_System::DoesStrippedFilenameHavePlatformVersions(uAnimID))
		{
			szAnimFileName = Asura_HashedFilename_System::GetFullFilename(uAnimID, false);
			Asura_Assertf(szAnimFileName, ("Failed to determine platform version for anim '%s'", m_xAnimFilename.GetString()));
		}

		if (szAnimFileName)
		{
			Tools_Level_Export::AddFileToExportList(xNameList, Asura_String(szAnimFileName), false);
		}
		else
		{
			Tools_Level_Export::AddFileToExportList(xNameList, m_xAnimFilename, false);
		}
	}
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_Mesh::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	const Asura_Hash_ID uSkinFileID = m_xSkinFilename.GetHash();
	if (uSkinFileID != ASURA_HASH_ID_UNSET)
	{
		const Asura_Hash_ID uSkinID = m_xSkinName.GetHash();
		Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::GetSkinFromID(uSkinID, uSkinFileID, false);
		if (!pxSkin)
		{
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Failed to load skin '%s'", m_xSkinFilename.GetString());
		}
	}

	const Asura_Hash_ID uAnimFileID = m_xAnimFilename.GetHash();
	if (uAnimFileID != ASURA_HASH_ID_UNSET)
	{
		const Asura_Hash_ID uAnimID = m_xAnimName.GetHash();
		Asura_Hierarchy_Anim* pxAnim = Tools_Animation_Files::GetAnimFromID(uAnimID, uAnimFileID, false);
		if (!pxAnim)
		{
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Failed to load anim '%s'", m_xAnimFilename.GetString());
		}
	}
}

/////////////////////
// AddMarkerPoints //
/////////////////////
// Add any marker point names to the given list.
void Tools_Container_Mesh::AddMarkerPoints(ListSelection_Overlay& xList, const Asura_Hash_ID uMarkerHash) const
{
	// Make sure that the skin is loaded and then add its marker points to the list.
	const Asura_Hash_ID uSkinFileID = m_xSkinFilename.GetHash();
	if (uSkinFileID != ASURA_HASH_ID_UNSET)
	{
		const Asura_Hash_ID uSkinID = m_xSkinName.GetHash();
		Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::GetSkinFromID(uSkinID, uSkinFileID, false);
		if (pxSkin && pxSkin->MarkerPoint)
		{
			const int iNum = pxSkin->NumberOfMarkerPoints;
			for (int i=0; i<iNum; ++i)
			{
				const Asura_Hierarchy_Marker_Point& xMarker = pxSkin->MarkerPoint[i];
				const Asura_String& xName = xMarker.GetName();

				if (!xName.IsBlank())
				{
					xList.AddItem(xName.GetString(), xMarker.uNameHash, xMarker.uNameHash == uMarkerHash, 0, true);
				}
			}
		}
	}
}

///////////////
// HasMarker //
///////////////
// Does the skin have the specified marker point?
bool Tools_Container_Mesh::HasMarker(Asura_Hash_ID uMarkerHash) const
{
	const Asura_Hash_ID uSkinFileID = m_xSkinFilename.GetHash();
	if (uSkinFileID != ASURA_HASH_ID_UNSET)
	{
		const Asura_Hash_ID uSkinID = m_xSkinName.GetHash();
		Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::GetSkinFromID(uSkinID, uSkinFileID, false);
		if (pxSkin && pxSkin->MarkerPoint)
		{
			const int iNum = pxSkin->NumberOfMarkerPoints;
			for (int i=0; i<iNum; ++i)
			{
				const Asura_Hierarchy_Marker_Point& xMarker = pxSkin->MarkerPoint[i];
				if (xMarker.uNameHash == uMarkerHash)
				{
					return true; // Found
				}
			}
		}
	}

	return false; // Not found
}

///////////////////////////////////
// HasSharedDynamicPhysicsObject //
///////////////////////////////////
// Is the mesh container flagged to have shared dynamic physics?
bool Tools_Container_Mesh::HasSharedDynamicPhysicsObject() const
{
	if (m_uContainerMeshFlags & ASURA_CONTAINER_MESH_FLAG_NO_COLLISIONS)
	{
		return false; // No collisions.
	}

	if ((m_uContainerMeshFlags & ASURA_CONTAINER_MESH_FLAG_PHYSICS_OBJECT) == 0)
	{
		return false; // Not dynamic physics.
	}

	if (m_uContainerMeshFlags & ASURA_CONTAINER_MESH_FLAG_HAS_OWN_PHYSICS)
	{
		return false; // Not shared.
	}

	return true;
}

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_SoundEvent
///////////////////////////////////////////////////////////////////////////////

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_SOUNDEVENT = 1;

void Tools_Container_SoundEvent::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_SOUNDEVENT)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_SoundEvent", iVersion));
		return;
	}

	xStream >> m_uSoundEventHash;

	if (iVersion >= 1)
	{
		xStream >> m_uSEFlags;
	}

	PARENT::ReadFromChunkStream(xStream);

	RefreshDialogue();
}

void Tools_Container_SoundEvent::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_SOUNDEVENT;

	xStream << m_uSoundEventHash;
	xStream << m_uSEFlags;

	PARENT::WriteToChunkStream(xStream);
}

void Tools_Container_SoundEvent::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	if (!m_pxAsuraContainer) return;

	// Refresh the dynamic string labels.
	RefreshDialogue();

	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_Dynamic_Dialog(&m_xSoundEventNameDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog("Select Sound Event", xOffset, SelectSoundEvent, reinterpret_cast<int>(this));
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Transition only
	pxDialog = new Asura_FlagTickbox_Dialog("Enable for transition only", xOffset, &m_uSEFlags, ASURA_CONTAINER_SE_FLAG_TRANSITION_ONLY, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Sound is only played during transitions caused by damage or receiving a state change message. The sound won't play when loading the level, or returning to a checkpoint, or activating the entity. Use this for transient damage sounds.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
}

/////////////////////
// RefreshDialogue //
/////////////////////
void Tools_Container_SoundEvent::RefreshDialogue()
{
	if (m_uSoundEventHash == ASURA_HASH_ID_UNSET)
	{
		m_xSoundEventName = "*None*";
	}
	else
	{
		const Asura_Sound_Event* pxEvent = Asura_Sound_Event_Storage::GetEventByHash(m_uSoundEventHash);
		if (pxEvent)
		{
			m_xSoundEventName = pxEvent->GetName();
		}
		else
		{
			m_xSoundEventName = "*Unknown*";
		}
	}

	m_xSoundEventNameDialogue = "SE: ";
	m_xSoundEventNameDialogue += m_xSoundEventName;
}

//////////////////////
// SelectSoundEvent //
//////////////////////
void Tools_Container_SoundEvent::SelectSoundEvent()
{
	Asura_Container_SoundEvent* pxContainerSE = static_cast<Asura_Container_SoundEvent*>(m_pxAsuraContainer);
	if (!pxContainerSE) return;

	Tools_PickSoundEvent_Overlay xOverlay("Select Sound Event");

	const Asura_Hash_ID uSoundEventInitial = pxContainerSE->m_uSoundEventHash;
	const Asura_Hash_ID uSoundEvent = xOverlay.DoModal(uSoundEventInitial);

	if (xOverlay.WasCancelled())
	{
		return;
	}

	if (uSoundEvent != uSoundEventInitial)
	{
		m_uSoundEventHash = uSoundEvent;

		CopyDataToAsura();
		RefreshDialogue();
		ResizeOverlay();
		SetModified();
	}
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_SoundEvent::CopyDataToAsura()
{
	Asura_Container_SoundEvent* pxContainerSE = static_cast<Asura_Container_SoundEvent*>(m_pxAsuraContainer);
	if (pxContainerSE)
	{
		pxContainerSE->m_uSoundEventHash = m_uSoundEventHash;
		pxContainerSE->m_uSEFlags = m_uSEFlags;
	}

	PARENT::CopyDataToAsura();
}

////////////
// Verify //
////////////
void Tools_Container_SoundEvent::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	Asura_Container_SoundEvent* pxContainerSE = static_cast<Asura_Container_SoundEvent*>(m_pxAsuraContainer);
	if (pxContainerSE)
	{
		const Asura_Hash_ID uSEHash = pxContainerSE->m_uSoundEventHash;
		if (uSEHash == ASURA_HASH_ID_UNSET)
		{
			AddLogMessage( "sound event not set." );
		}
		else
		{
			// Check whether SE exists.
			const Asura_Sound_Event* pxEvent = Asura_Sound_Event_Storage::GetEventByHash(m_uSoundEventHash);
			if (!pxEvent)
			{
				AddLogMessage( "sound event (0x%08x) not found.", uSEHash );
			}
		}
	}
}

/////////////////
// ToolsRender //
/////////////////
// Render a sphere, to show that there is a sound event here.
void Tools_Container_SoundEvent::ToolsRender(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOri) const
{
	if (!Tools_System::IsActive()) return;
	if (!Tools_Container_System::ShouldDisplaySoundEvents()) return;
	
	extern void Render_Sphere( const Asura_Vector_3& xCentre, float fRadius, int iColour );

	if (m_uSoundEventHash != ASURA_HASH_ID_UNSET)
	{
		float fInnerRadius = 0.5f;
		float fOuterRadius = 0.5f;

		const Asura_Sound_Event* pxEvent = Asura_Sound_Event_Storage::GetEventByHash(m_uSoundEventHash);
		if (pxEvent)
		{
			fInnerRadius = pxEvent->GetInnerRadius();
			fOuterRadius = pxEvent->GetOuterRadius();
		}

		Render_Sphere(xPosition, fInnerRadius, 0x7f0000ff);
		Render_Sphere(xPosition, fOuterRadius, 0x7fff0000);
	}
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_SoundEvent::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	if (m_uSoundEventHash != ASURA_HASH_ID_UNSET)
	{
		Tools_Level_Export::AddSoundEventToExportList(m_uSoundEventHash);
	}
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_SoundEvent::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_PFX_SourceVolume_Object
///////////////////////////////////////////////////////////////////////////////
class Tools_Container_PFX_SourceVolume_Object : public Tools_PFX_SourceVolume_Object
{
public:
	Tools_Container_PFX_SourceVolume_Object(const Asura_PFX_Volume& xVolume)
		: Tools_PFX_SourceVolume_Object(xVolume)
	{}

	virtual bool CanBeMoved() { return false; }
};


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_PFX
///////////////////////////////////////////////////////////////////////////////

Tools_Container_PFX::Tools_Container_PFX()
: m_uEffectTemplateHash(ASURA_HASH_ID_UNSET)
, m_fMinRetriggerTime(0.0f)
, m_fMaxRetriggerTime(0.0f)
, m_bSkyboxEffect(false)
, m_bWaterDistorterEffect(false)
, m_bIgnoreModuleLinking(false)
, m_uPFXFlags(0)
, m_pxToolsSourceVolumeEdit(0)
{
	RefreshDialogue();
}

Tools_Container_PFX::~Tools_Container_PFX()
{
	if (m_pxToolsSourceVolumeEdit)
	{
		delete m_pxToolsSourceVolumeEdit;
		m_pxToolsSourceVolumeEdit = 0;
	}
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_PFX = 3;

void Tools_Container_PFX::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_PFX)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_PFX", iVersion));
		return;
	}

	xStream >> m_uEffectTemplateHash;

	if (iVersion >= 1)
	{
		xStream >> m_fMinRetriggerTime;
		xStream >> m_fMaxRetriggerTime;

		xStream >> m_bSkyboxEffect;
		xStream >> m_bWaterDistorterEffect;
		xStream >> m_bIgnoreModuleLinking;
		xStream.Skip4BytePadding();
	}

	if (iVersion >= 2)
	{
		m_xSourceVolume.ReadFromChunkStream(xStream);
	}

	if (iVersion >= 3)
	{
		xStream >> m_uPFXFlags;
	}

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_PFX::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_PFX;

	xStream << m_uEffectTemplateHash;

	xStream << m_fMinRetriggerTime;
	xStream << m_fMaxRetriggerTime;

	xStream << m_bSkyboxEffect;
	xStream << m_bWaterDistorterEffect;
	xStream << m_bIgnoreModuleLinking;
	xStream.Write4BytePadding();

	m_xSourceVolume.WriteToChunkStream(xStream);

	xStream << m_uPFXFlags;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// RefreshDialogue //
/////////////////////
void Tools_Container_PFX::RefreshDialogue()
{
	if (m_uEffectTemplateHash == ASURA_HASH_ID_UNSET)
	{
		m_xPFXName = "*None*";
	}
	else
	{
		const Asura_PFX_Effect_Template* pxEffect = Asura_PFX_System::GetEffectTempByHash(m_uEffectTemplateHash);
		if (pxEffect)
		{
			m_xPFXName = pxEffect->m_xName;
		}
		else
		{
			m_xPFXName = "*Not Found*";
		}
	}
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_PFX::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	if (!m_pxAsuraContainer) return;

	Asura_Dialog* pxDialog = 0;

	// PFX effect template
	pxDialog = new Tools_PFXSelect_Dialog(Tools_PFXSelect_Dialog::MODE_EFFECTS, xOffset, &m_uEffectTemplateHash, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Select PFX.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Edit volume source
	pxDialog = new Asura_Function_Dialog("Edit source volume", xOffset, EditSourceVolume, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("This allows the source volume in which particles get generated to be overriden for this container.\nIf the volume is set as anything other than point, then it will be used for all particle sources.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Ignore visibility
	pxDialog = new Asura_Tickbox_Dialog("Ignore Visibility System", xOffset, &m_bIgnoreModuleLinking, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Skybox effect
	pxDialog = new Asura_Tickbox_Dialog("Skybox effect", xOffset, &m_bSkyboxEffect, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Water distorter
#ifdef ASURA_TARGET_SM3
	pxDialog = new Asura_Tickbox_Dialog("Water Distorter effect", xOffset, &m_bWaterDistorterEffect, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
#endif

	// Transition only
	pxDialog = new Asura_FlagTickbox_Dialog("Enable for transition only", xOffset, &m_uPFXFlags, ASURA_CONTAINER_PFX_FLAG_TRANSITION_ONLY, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Effect only occurs during transitions caused by damage or receiving a state change message. The effect won't happen when loading the level, or returning to a checkpoint, or activating the entity. Use this for transient damage effects.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Retrigger the PFX on state changes that have the PFX flagged as enabled, if the PFX has already stopped.
	pxDialog = new Asura_FlagTickbox_Dialog("Allow reretrigger on state change if stopped", xOffset, &m_uPFXFlags, ASURA_CONTAINER_PFX_FLAG_ALLOW_RETRIGGER_IF_STOPPED, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If an effect has started and stopped in one state, and the container changes state to another with the effect enabled, then the effect will retrigger.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Retrigger time
	pxDialog = new Asura_Numerical_Dialog("Retrigger Min", xOffset, &m_fMinRetriggerTime, 0.f, 999.0f, .1f, "%.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If set to a non-zero value then the effect will randomly retrigger based on these times whenever it finishes (while active).");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Numerical_Dialog("Retrigger Max", xOffset, &m_fMaxRetriggerTime, 0.f, 999.0f, .1f, "%.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If set to a non-zero value then the effect will randomly retrigger based on these times whenever it finishes (while active).");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

///////////////////////////
// PerFrameOverlayUpdate //
///////////////////////////
void Tools_Container_PFX::PerFrameOverlayUpdate()
{
	if (!m_pxToolsSourceVolumeEdit) return;

	if (m_pxToolsSourceVolumeEdit->m_bReadyToDelete)
	{
		delete m_pxToolsSourceVolumeEdit;
		m_pxToolsSourceVolumeEdit = 0;
		return;
	}

	if (!m_pxToolsSourceVolumeEdit->m_bGameEntitiesNeedUpdating) return;

	// Update source volume
	m_xSourceVolume = m_pxToolsSourceVolumeEdit->m_xVolume;
	
	// Reset flag
	m_pxToolsSourceVolumeEdit->m_bGameEntitiesNeedUpdating = false;

	// Ensure that the instance is updated.
	s_bChangedEditOverlay = true;
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_PFX::CopyDataToAsura()
{
	bool bRefresh = false;

	Asura_Container_PFX* pxContainerPFX = static_cast<Asura_Container_PFX*>(m_pxAsuraContainer);
	Asura_Assert(pxContainerPFX, "Null pointer, container not set.");
	if (pxContainerPFX)
	{
		Asura_Entity_PFX_Effect_Data& xData = pxContainerPFX->m_xPFXData;

		if (xData.m_uEffectTemplateHash != m_uEffectTemplateHash)
		{
			bRefresh = true;
			xData.m_uEffectTemplateHash = m_uEffectTemplateHash;
		}

		// Check max and min are ordered correctly.
		const float fMinTime = Asura_Maths::Min(m_fMinRetriggerTime, m_fMaxRetriggerTime);
		const float fMaxTime = Asura_Maths::Max(m_fMinRetriggerTime, m_fMaxRetriggerTime);

		m_fMinRetriggerTime = fMinTime;
		m_fMaxRetriggerTime = fMaxTime;

		xData.m_fMinRetriggerTime = m_fMinRetriggerTime;
		xData.m_fMaxRetriggerTime = m_fMaxRetriggerTime;

		xData.m_bSkyboxEffect		  = m_bSkyboxEffect;
		xData.m_bWaterDistorterEffect = m_bWaterDistorterEffect;
		xData.m_bIgnoreModuleLinking  = m_bIgnoreModuleLinking;

		xData.m_xSourceVolume = m_xSourceVolume;

		pxContainerPFX->m_uPFXFlags = m_uPFXFlags;
	}

	PARENT::CopyDataToAsura();

	if (bRefresh && (s_bChangedEditOverlay || s_bChangedEditOverlay_MinorChange))
	{
		RefreshDialogue();
	}
}


//////////////////////
// EditSourceVolume //
//////////////////////
void Tools_Container_PFX::EditSourceVolume()
{
	if (!m_pxToolsSourceVolumeEdit)
	{
		m_pxToolsSourceVolumeEdit = new Tools_Container_PFX_SourceVolume_Object(m_xSourceVolume);
	}
}

/////////////////
// ToolsRender //
/////////////////
void Tools_Container_PFX::ToolsRender(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOri) const
{
	if (m_pxToolsSourceVolumeEdit)
	{
		static u_int s_uLastFrameNumber = 0;
		if (s_uLastFrameNumber != Asura_Timers::GetFrameNumber())
		{
			s_uLastFrameNumber = Asura_Timers::GetFrameNumber();
			m_pxToolsSourceVolumeEdit->SetPositionAndOrient(xPosition, xOri);
		}
	}
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_PFX::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	Asura_Container_PFX* pxContainerPFX = static_cast<Asura_Container_PFX*>(m_pxAsuraContainer);
	Asura_Assert(pxContainerPFX, "Null pointer, container not set.");
	if (pxContainerPFX)
	{
		// We need to add the effect, source, and particles templates for this effect.
		if (!Tools_Mode_PFX::AddTemplateToExportListByHash(&xNameList, pxContainerPFX->m_xPFXData.m_uEffectTemplateHash, ASURA_GUID_UNREGISTERED))
		{
			Tools_Timed_Message_System::AddMessage(0xffff00, "Warning: PFX for container '%s' has no effect template.", GetContainerName().GetString());
		}
	}
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_PFX::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	if (m_uEffectTemplateHash != ASURA_HASH_ID_UNSET)
	{
		// Force the PFX template to be loaded, if not already done.
#ifdef ASURA_ASSERT
		Asura_PFX_Effect_Template* pxTemplate =
#endif //ASURA_ASSERT
			Asura_PFX_System::GetEffectTempByHash(m_uEffectTemplateHash);

		Asura_Assertf(pxTemplate, ("Failed to load PFX template (0x%08x) from container '%s'", m_uEffectTemplateHash, GetContainerName().GetString()));
	}
}

////////////
// Verify //
////////////
void Tools_Container_PFX::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that the templates and graphics are loaded.
	LoadRequiredFiles();

	if (m_uEffectTemplateHash == ASURA_HASH_ID_UNSET)
	{
		AddLogMessage("PFX not set.");
	}
	else if (Asura_PFX_System::GetEffectTempByHash(m_uEffectTemplateHash) == 0)
	{
		AddLogMessage("PFX effect template '0x%08x' not found.", m_uEffectTemplateHash);
	}
}


///////////////////////
// AddOverlay_Corona //
///////////////////////
// Helper to add corona dialogues to overlay or tab.
static void AddOverlay_Corona(const Asura_OverlayOrTab& xOverlayOrTab, Asura_Vector_2& xOffset,
							  Asura_Container_Corona_Param& xCoronaParam, Asura_String& xCoronaTextureName, float* pfAngleRange,
							  bool* pbModifiedFlag)
{
	const float fTEXTURE_PICKER_SIZE = 64.0f;
	Asura_Vector_2 xSavedOffset(xOffset);

	// Texture
	Asura_Dialog* pxDialog = 0;
	pxDialog = new Asura_TexturePicker_Dialog( "Texture", xOffset, NULL, &xCoronaParam.m_uCoronaTextureHash, &xCoronaTextureName,
												fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, pbModifiedFlag );
	xOverlayOrTab.AddDialog( pxDialog );
	const float fOffsetTwoX = pxDialog->GetMaxCoords().x;

	// Colour
	xOffset.x = fOffsetTwoX + 8.0f;
	pxDialog = new Asura_Dialog("Colour", xOffset);
	xOverlayOrTab.AddDialog( pxDialog );

	xOffset.x = fOffsetTwoX + 8.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();

	Asura_RGBA& xColour = xCoronaParam.m_xCoronaColour;
	Tools_ColourPicker_Dialog* pxColourPickerDialog =
					new Tools_ColourPicker_Dialog("Colour",	xOffset, Asura_Vector_2(3.0f, 1.2f),
												 &xColour.m_fR, &xColour.m_fG, &xColour.m_fB,
												 1.0f, pbModifiedFlag);
	pxColourPickerDialog->EnableHDR(true);
	xOverlayOrTab.AddDialog( pxColourPickerDialog );

	xOffset.x = xSavedOffset.x;
	xOffset.y += Asura_Dialog::LargeSpacing() * 2.5f;
	
	// Radius
	pxDialog = new Asura_Numerical_Dialog("Radius", xOffset, &xCoronaParam.m_fCoronaSize, 0.0f, 100.0f, 0.1f, "%0.2f", pbModifiedFlag);
	pxDialog->SetHelpText("Radius of corona in metres.");
	xOverlayOrTab.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Depth
	pxDialog = new Asura_Numerical_Dialog("Volumetric Depth Scale", xOffset, &xCoronaParam.m_fCoronaDepth, 0.0f, 100.0f, 0.1f, "%0.2f", pbModifiedFlag);
	pxDialog->SetHelpText("Controls softness of corona when it intersects other geometry");
	xOverlayOrTab.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Angle range (for lightshafts)
	if (pfAngleRange)
	{
		pxDialog = new Asura_Numerical_Dialog("Angle Range", xOffset, pfAngleRange, 0.0f, 1.0f, 0.1f, "%0.4f", pbModifiedFlag);
		xOverlayOrTab.AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::LargeSpacing();
	}
}

///////////////////////////////
// AddOverlay_Corona_Options //
///////////////////////////////
// Helper to add corona dialogues to overlay or tab.
static void AddOverlay_Corona_Options(const Asura_OverlayOrTab& xOverlayOrTab, Asura_Vector_2& xOffset,
									  Asura_Container_Corona_Param& xCoronaParam, bool* pbModifiedFlag)
{
	Asura_Dialog* pxDialog = 0;

	// Use strobe
	pxDialog = new Asura_FlagTickbox_Dialog("Use Strobe", xOffset, &xCoronaParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_USE_STROBE, pbModifiedFlag);
	pxDialog->SetHelpText("If ticked then the corona/lightshaft will be affected by any active Strobe in the same collection.");
	xOverlayOrTab.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Use graph colour
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Colour", xOffset, &xCoronaParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_USE_GRAPH_COLOUR, pbModifiedFlag);
	pxDialog->SetHelpText("If ticked then the corona/lightshaft will be affected by any active GraphColour, in the same collection, that has 'Colour' enabled.");
	xOverlayOrTab.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Use graph flicker
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Flicker", xOffset, &xCoronaParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_USE_GRAPH_FLICKER, pbModifiedFlag);
	pxDialog->SetHelpText("If ticked then the corona/lightshaft will be affected by any active GraphColour, in the same collection, that has 'Flicker' enabled.");
	xOverlayOrTab.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
}

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Corona
///////////////////////////////////////////////////////////////////////////////
Tools_Container_Corona::Tools_Container_Corona()
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_CORONA = 0;

void Tools_Container_Corona::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_CORONA)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Corona", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);

	xStream >> m_xCoronaTextureName;
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Corona::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_CORONA;

	m_xParam.WriteToChunkStream(xStream);

	xStream << m_xCoronaTextureName;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Corona::CopyDataToAsura()
{
	Asura_Container_Corona* pxContainerCorona = static_cast<Asura_Container_Corona*>(m_pxAsuraContainer);
	Asura_Assert(pxContainerCorona, "Null pointer, container not set.");
	if (pxContainerCorona)
	{
		pxContainerCorona->m_xParam = m_xParam;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Corona::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	AddOverlay_Corona(&xNewOverlay, xOffset, m_xParam, m_xCoronaTextureName, 0, &s_bChangedEditOverlay);
	AddOverlay_Corona_Options(&xNewOverlay, xOffset, m_xParam, &s_bChangedEditOverlay);

	// Fade with distance
	pxDialog = new Asura_FlagTickbox_Dialog("Don't fade with distance", xOffset, &m_xParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_NO_DISTANCE_FADEOUT, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Visibilty test.
	pxDialog = new Asura_FlagTickbox_Dialog("Uses visibility test", xOffset, &m_xParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_USE_VISIBILTY_TEST, &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Direction fadeout effect.
	pxDialog = new Asura_FlagTickbox_Dialog("Use orientation fadeout", xOffset, &m_xParam.m_uCoronaFlags, ASURA_CONTAINER_CORONA_FLAG_USE_DIRECTION_FADEOUT, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("When ticked the corona will fade out if not viewed head on.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

///////////////////////////
// PerFrameOverlayUpdate //
///////////////////////////
void Tools_Container_Corona::PerFrameOverlayUpdate()
{
	// Workaround for a bug in the texture picker. When "escape" is pressed it clears the hash, but leaves the string alone.
	if (m_xParam.m_uCoronaTextureHash == ASURA_HASH_ID_UNSET && !m_xCoronaTextureName.IsBlank())
	{
		m_xParam.m_uCoronaTextureHash = Asura_GetHashID_IgnoreExtension(m_xCoronaTextureName.GetString());
	}
}

///////////////////////////////////
// TraverseUsedTexturesForReport //
///////////////////////////////////
void Tools_Container_Corona::TraverseUsedTexturesForReport(void (*pfnCallback)(int iTextureID,void* pUserData), void* pUserData) const
{
	const int iTexID = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_uCoronaTextureHash );
	pfnCallback(iTexID, pUserData);
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_Corona::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	AddTextureToExportList(m_xParam.m_uCoronaTextureHash, m_xCoronaTextureName);
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_Corona::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	LoadRequiredTextureFile(m_xParam.m_uCoronaTextureHash, m_xCoronaTextureName);
}

////////////
// Verify //
////////////
void Tools_Container_Corona::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that any textures are loaded.
	LoadRequiredFiles();

	VerifyTexture(m_xParam.m_uCoronaTextureHash, m_xCoronaTextureName, "corona");
}

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_LightShaft
///////////////////////////////////////////////////////////////////////////////
#ifdef ASURA_TARGET_SM3

Tools_Container_LightShaft::Tools_Container_LightShaft()
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_LIGHTSHAFT = 0;

void Tools_Container_LightShaft::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_LIGHTSHAFT)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_LightShaft", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);

	xStream >> m_axTextureName[0];
	xStream >> m_axTextureName[1];
	xStream >> m_xCoronaTextureName;
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_LightShaft::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_LIGHTSHAFT;

	m_xParam.WriteToChunkStream(xStream);

	xStream << m_axTextureName[0];
	xStream << m_axTextureName[1];
	xStream << m_xCoronaTextureName;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_LightShaft::CopyDataToAsura()
{
	Asura_Container_LightShaft* pxContainer = static_cast<Asura_Container_LightShaft*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_xParam = m_xParam;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_LightShaft::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	const float fTEXTURE_PICKER_SIZE = 64.0f;

	// Create the tabs
	static const char* const s_aszTabNames[] = { "Beam", "Corona" };
	const u_int uNumTabs = sizeof(s_aszTabNames) / sizeof(s_aszTabNames[0]);

	Asura_Dialog* pxDialog = 0;
	Asura_Tabs_Dialog* pxTabs = 0;

	pxDialog = pxTabs = new Asura_Tabs_Dialog(xOffset, s_aszTabNames, uNumTabs);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// ======================================================================================================
	// 'BEAM' tab
	// ======================================================================================================
	pxTabs->SelectTab(0);

	const Asura_Vector_2 xSavedOffset(xOffset);

	for (int i=0; i<2; i++)
	{
		const float fOffsetX = xSavedOffset.x + (i*21.0f);
		xOffset.x = fOffsetX;
		xOffset.y = xSavedOffset.y;

		// Title near/far
		pxDialog = new Asura_Dialog(i==0 ? "Near" : "Far", xOffset);
		pxTabs->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		// Distance
		pxDialog = new Asura_Numerical_Dialog("Distance", xOffset, &m_xParam.m_afDistance[i], 0.0f, 2500.0f, 0.25f, "%0.1f", &s_bChangedEditOverlay);
		pxTabs->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		// Radius
		pxDialog = new Asura_Numerical_Dialog("Radius", xOffset, &m_xParam.m_afRadius[i] , 0.0f, 250.0f, 0.1f, "%0.2f", &s_bChangedEditOverlay);
		pxTabs->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::LargeSpacing();

		// Texture A/B
		pxDialog = new Asura_TexturePicker_Dialog( (i==0) ? "Texture A" : "Texture B", xOffset, NULL, &m_xParam.m_auTextureHash[i], &m_axTextureName[i], fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, &s_bChangedEditOverlay );
		pxTabs->AddDialog( pxDialog );				
		
		// Colour
		const float fOffsetTwoX = pxDialog->GetMaxCoords().x;
		xOffset.x = fOffsetTwoX + 6.0f;
		pxDialog = new Asura_Dialog("Colour", xOffset);
		pxTabs->AddDialog( pxDialog );

		xOffset.x = fOffsetTwoX + 6.0f;
		xOffset.y += Asura_Dialog::LargeSpacing();

		Asura_RGBA& xColour = m_xParam.m_axColour[i];

		Tools_ColourPicker_Dialog* pxColourPickerDialog =
					new Tools_ColourPicker_Dialog("Colour",	xOffset, Asura_Vector_2(3.0f, 1.2f),
												 &xColour.m_fR, &xColour.m_fG, &xColour.m_fB,
												 1.0f, &s_bChangedEditOverlay);
		pxColourPickerDialog->EnableHDR(true);
		pxTabs->AddDialog( pxColourPickerDialog );

		xOffset.x = fOffsetX;
		xOffset.y += Asura_Dialog::LargeSpacing() * 2.5f;


		// U Repeat
		pxDialog = new Asura_Numerical_Dialog("U Repeats", xOffset, &m_xParam.m_afTextureUScale[i] , 0.0f, 100.0f, 1.0f, "%0.4f", &s_bChangedEditOverlay);
		pxTabs->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		// U Scroll
		pxDialog = new Asura_Numerical_Dialog("U Scroll", xOffset, &m_xParam.m_afTextureScrollRate[i] , -25.0f, 25.0f, 0.01f, "%0.4f", &s_bChangedEditOverlay);
		pxTabs->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::LargeSpacing();
	}

	xOffset.x = xSavedOffset.x;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	float fTabBottom = xOffset.y;


	// ======================================================================================================
	// 'CORONA' tab
	// ======================================================================================================
	pxTabs->SelectTab(1);
	xOffset = xSavedOffset;

	AddOverlay_Corona(pxTabs, xOffset, m_xParam.m_xCoronaParam, m_xCoronaTextureName, &m_xParam.m_fCoronaAngleRange, &s_bChangedEditOverlay);

	fTabBottom = Asura_Maths::Max(fTabBottom, xOffset.y);
	pxTabs->SelectTab(0);

	xOffset.x = xSavedOffset.x;
	xOffset.y = fTabBottom;

	static u_int s_uTabSelection = 0;
	pxTabs->SetSelectedTabPointer(&s_uTabSelection);

	// Options
	AddOverlay_Corona_Options(&xNewOverlay, xOffset, m_xParam.m_xCoronaParam, &s_bChangedEditOverlay);

	// Distance fade
	pxDialog = new Asura_Numerical_Dialog("Close Fade: Start Dist", xOffset, &m_xParam.m_fCloseFadeStart, 0.0f, 2500.0f, 1.0f, "%0.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("For fading out (and disabling) the light shaft based on distance from the camera.");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Close Fade: Range", xOffset, &m_xParam.m_fCloseFadeRange, 0.0f, 2500.0f, 1.0f, "%0.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("For fading out (and disabling) the light shaft based on distance from the camera.");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Numerical_Dialog("Distance Fade: Start Dist", xOffset, &m_xParam.m_fDistFadeStart, 0.0f, 2500.0f, 1.0f, "%0.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("For fading out (and disabling) the light shaft based on distance from the camera.");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Distance Fade: Range", xOffset, &m_xParam.m_fDistFadeRange, 0.0f, 2500.0f, 1.0f, "%0.1f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("For fading out (and disabling) the light shaft based on distance from the camera.");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

///////////////////////////
// PerFrameOverlayUpdate //
///////////////////////////
void Tools_Container_LightShaft::PerFrameOverlayUpdate()
{
	// Workaround for a bug in the texture picker. When "escape" is pressed it clears the hash, but leaves the string alone.
	for (int i=0; i<2; ++i)
	{
		if (m_xParam.m_auTextureHash[i] == ASURA_HASH_ID_UNSET && !m_axTextureName[i].IsBlank())
		{
			m_xParam.m_auTextureHash[i] = Asura_GetHashID_IgnoreExtension(m_axTextureName[i].GetString());
		}
	}

	if (m_xParam.m_xCoronaParam.m_uCoronaTextureHash == ASURA_HASH_ID_UNSET && !m_xCoronaTextureName.IsBlank())
	{
		m_xParam.m_xCoronaParam.m_uCoronaTextureHash = Asura_GetHashID_IgnoreExtension(m_xCoronaTextureName.GetString());
	}
}

///////////////////////////////////
// TraverseUsedTexturesForReport //
///////////////////////////////////
void Tools_Container_LightShaft::TraverseUsedTexturesForReport(void (*pfnCallback)(int iTextureID,void* pUserData), void* pUserData) const
{
	for (int i=0; i<2; ++i)
	{
		const int iTexID = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_auTextureHash[i] );
		pfnCallback(iTexID, pUserData);
	}

	const int iTexID = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_xCoronaParam.m_uCoronaTextureHash );
	pfnCallback(iTexID, pUserData);
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_LightShaft::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	for (int i=0; i<2; ++i)
	{
		AddTextureToExportList(m_xParam.m_auTextureHash[i], m_axTextureName[i]);	
	}

	AddTextureToExportList(m_xParam.m_xCoronaParam.m_uCoronaTextureHash, m_xCoronaTextureName);
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_LightShaft::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	for (int i=0; i<2; ++i)
	{
		LoadRequiredTextureFile(m_xParam.m_auTextureHash[i], m_axTextureName[i]);
	}

	LoadRequiredTextureFile(m_xParam.m_xCoronaParam.m_uCoronaTextureHash, m_xCoronaTextureName);
}

////////////
// Verify //
////////////
void Tools_Container_LightShaft::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that the textures are loaded.
	LoadRequiredFiles();

	for (int i=0; i<2; ++i)
	{
		VerifyTexture(m_xParam.m_auTextureHash[i], m_axTextureName[i], ((i==0) ? "A" : "B"));
	}
}
#endif //#ifdef ASURA_TARGET_SM3


///////////////////////////////////////////////////////////////////////////////
// Asura_DropDownList_Masked_Dialog
///////////////////////////////////////////////////////////////////////////////
// As Asura_DropDown_List_Dialog, but reads/writes to the least significant bits of a u_int that has a mask applied to it.
class Asura_DropDownList_Masked_Dialog : public Asura_DropDown_List_Dialog
{
public:
	Asura_DropDownList_Masked_Dialog( const Asura_Vector_2& xOffset,
									  const char* const* ppszList,
									  u_int& uSelection,	// Selection
									  u_int uSelectionMask,	// Mask
									  int iNumberOfLines,
									  int iNumberInList,
									  void( *pfFunction )( void ),
									  bool* pbChangedFlag = 0,
									  const char* szName = NULL,
									  const bool bOnlySelectOnClick = false )
	: Asura_DropDown_List_Dialog(xOffset, ppszList, GetSelectionAddress(uSelection & uSelectionMask), iNumberOfLines, iNumberInList, pfFunction, pbChangedFlag, szName, bOnlySelectOnClick)
	, m_uSelection(uSelection)
	, m_uSelectionMask(uSelectionMask)
	, m_uLastValue(uSelection & uSelectionMask)
	{
		m_iSelection = static_cast<int>(m_uLastValue);
	}

	void PerFrameUpdate()
	{
		// Check whether value has changed externally.
		if ((m_uSelection & m_uSelectionMask) != m_uLastValue)
		{
			m_uLastValue = (m_uSelection & m_uSelectionMask);
			m_iSelection = static_cast<int>(m_uLastValue);
			return;
		}

		// Check whether user has changed the value.
		if (m_iSelection >= 0)
		{
			const u_int uNewValue = (static_cast<u_int>(m_iSelection) & m_uSelectionMask);
			m_uLastValue = uNewValue;
			
			m_uSelection = ((m_uSelection & ~m_uSelectionMask) | m_uLastValue);
		}
	}

	// MGH 25/7/2008: Workaround for crash caused by uninitialised data, due to general rubbishness of Tools UI code.
	// We need to initialise m_iSelection *before* the base class constructor is called.
	int* GetSelectionAddress(int iInitValue)
	{
		m_iSelection = iInitValue;
		return &m_iSelection;
	}

protected:
	int m_iSelection;				// Local copy of selection
	u_int& m_uSelection;			// External variable to write selection to.
	const u_int m_uSelectionMask;	// Mask to use.
	u_int m_uLastValue;				// Last known value of selection.
};

// Projector blend and light modes.
const u_int uNUM_PROJECTOR_BLEND_MODES = 7;
static const char * const g_aszProjectorBlendModes[uNUM_PROJECTOR_BLEND_MODES] =
{
	"Fake Light",			//0 (ASURA_CONTAINER_PROJECTOR_FLAG_FAKE_LIGHT)
	"Dynamic Light",		//1 (ASURA_CONTAINER_PROJECTOR_FLAG_DYNAMIC_LIGHT)
	"Spotlight",			//2 (ASURA_CONTAINER_PROJECTOR_FLAG_DYNAMIC_SPOTLIGHT)
	"Additive",				//3 (ASURA_CONTAINER_PROJECTOR_FLAG_BLEND_ADD)
	"Multiply (0-100%)",	//4 (ASURA_CONTAINER_PROJECTOR_FLAG_BLEND_MULTIPLY)
	"Multiply X2 (0-200%)",	//5 (ASURA_CONTAINER_PROJECTOR_FLAG_BLEND_MULTIPLYx2)
	"Standard Alpha Blend"	//6 (ASURA_CONTAINER_PROJECTOR_FLAG_BLEND_STANDARD)
};



///////////////////////////////////////////////////////////////////////////////
// Tools_Container_LimitingPlane_Object
///////////////////////////////////////////////////////////////////////////////
#ifdef ASURA_TARGET_SM3
class Tools_Container_LimitingPlane_Object : public Tools_DynamicLight_LimitingPlane_Object
{
public:
	Tools_Container_LimitingPlane_Object()
	{
		m_xTypeDescription = "Projector Container Limiting Plane";
		m_xBasePos.Zero();
		m_xBaseOri.Identity();
	}

	void SetBasePosOri(const Asura_Vector_3& xBasePos, const Asura_Matrix_3x3& xBaseOri)
	{
		if (xBasePos != m_xBasePos || xBaseOri != m_xBaseOri)
		{
			// Base pos/ori has changed. Move the clipping plane.

			// Translate.
			Asura_Vector_3 xRelPos = m_xBaseOri.ApplyTranspose(Position - m_xBasePos);
			Position = xRelPos * xBaseOri + xBasePos;
			
			m_xBaseOri.Transpose();
			Orientation = Orientation * m_xBaseOri * xBaseOri;
	
			m_xBasePos = xBasePos;
			m_xBaseOri = xBaseOri;
		}
	}

	const Asura_Vector_3&  GetBasePos() const { return m_xBasePos; }
	const Asura_Matrix_3x3& GetBaseOri() const { return m_xBaseOri; }

protected:
	Asura_Vector_3 m_xBasePos;
	Asura_Matrix_3x3 m_xBaseOri;
};
#endif //#ifdef ASURA_TARGET_SM3

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Projector
///////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_TARGET_SM3

Tools_Container_Projector::Tools_Container_Projector()
: m_pxLimitingPlane(0)
{
}

Tools_Container_Projector::~Tools_Container_Projector()
{
	if (m_pxLimitingPlane)
	{
		Tools_Object_System::DeselectObject(m_pxLimitingPlane);
		delete m_pxLimitingPlane;
		m_pxLimitingPlane = 0;
	}
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_PROJECTOR = 1;

void Tools_Container_Projector::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_PROJECTOR)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Projector", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);

	if (iVersion >= 1)
	{
		m_xShadowParam.ReadFromChunkStream(xStream);
	}

	xStream >> m_xTextureName;
	xStream >> m_xDetailTextureName;
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Projector::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_PROJECTOR;

	m_xParam.WriteToChunkStream(xStream);
	m_xShadowParam.WriteToChunkStream(xStream);

	xStream << m_xTextureName;
	xStream << m_xDetailTextureName;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Projector::CopyDataToAsura()
{
	Asura_Container_Projector* pxContainer = static_cast<Asura_Container_Projector*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_xParam = m_xParam;
		pxContainer->m_xShadowParam = m_xShadowParam;
	}

	PARENT::CopyDataToAsura();
}

// Constants
const float fCOLUMN_OFFSET_X = 25.0f;
const float fTEXTURE_PICKER_SIZE = 64.0f;

///////////////////////////
// AddOverlayTab_Falloff //
///////////////////////////
// Add tabs for fall-off.
static void AddOverlayTab_Falloff(Asura_Tabs_Dialog* pxTab, Asura_Vector_2& xOffset,
								  float& fFalloffInnerRatio, float& fFalloffPower,
								  float& fDistFadeStart, float& fDistFadeRange,
								  bool* pbChanged)
{
	if (!pxTab) return;

	Asura_Dialog* pxDialog = 0;

	// Add the graph
	pxDialog = new Tools_Dialog_Container_FalloffGraph(xOffset, 360, 120, fFalloffInnerRatio, fFalloffPower);	
	xOffset.y += 8.0f*Asura_Dialog::DefaultSpacing();
	pxTab->AddDialog(pxDialog);

	// Values that control the fall-off.
	pxDialog = new Asura_Numerical_Dialog("Inner radius ratio", xOffset, &fFalloffInnerRatio, 0.0f, 0.99f, 0.01f, "%0.2f", pbChanged);
	pxDialog->SetHelpText("Controls the 'inner radius' of the light - the area where it is at max strength");
	pxTab->AddDialog( pxDialog );
	xOffset.x += fCOLUMN_OFFSET_X;

	pxDialog = new Asura_Numerical_Dialog("Power", xOffset, &fFalloffPower , 0.2f, 5.0f, 0.01f, "%0.2f", pbChanged);
	pxDialog->SetHelpText("Controls the shape of the falloff curve");
	pxTab->AddDialog( pxDialog );
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::LargeSpacing();	

	// Distance fade out
	pxDialog = new Asura_Numerical_Dialog("Distance Fade: Start Dist", xOffset, &fDistFadeStart, 0.0f, 2500.0f, 1.0f, "%0.1f", pbChanged);
	pxDialog->SetHelpText("For fading out the light based on distance from the camera.");
	pxTab->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Distance Fade: Range", xOffset, &fDistFadeRange, 0.0f, 2500.0f, 1.0f, "%0.1f", pbChanged);
	pxDialog->SetHelpText("For fading out the light based on distance from the camera.");
	pxTab->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();
}


//////////////////////////////////////
// Tools_ShadowDropDown_List_Dialog //
//////////////////////////////////////
// Drop down dialog for shadow mode, converting list of modes in to relevant flags.
const int iNUM_SHADOW_MODES = 3;

class Tools_ShadowDropDown_List_Dialog : public Asura_DropDown_List_Dialog
{
public:
	Tools_ShadowDropDown_List_Dialog(const Asura_Vector_2& xOffset, Asura_ShadowParameters& xShadowParam, bool* pbChanged)
		: Asura_DropDown_List_Dialog(xOffset, s_aszShadowModes, GetSelectionIndex(xShadowParam),
									 iNUM_SHADOW_MODES, iNUM_SHADOW_MODES, NULL,
									 pbChanged, "Object Shadow Mode")
		, m_xShadowParam(xShadowParam)
	{}

	// Initialise selection index and return its address.
	int* GetSelectionIndex(const Asura_ShadowParameters& xShadowParam)
	{
		m_iSelection = 0;
		if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW)
		{
			m_iSelection = 1;
		}
		else if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS)
		{
			m_iSelection = 2;
		}
		m_iLastSelection = m_iSelection;
		return &m_iSelection;
	}

	// Write mode back to the parameters when it has been changed by the user.
	void PerFrameUpdate()
	{
		Asura_DropDown_List_Dialog::PerFrameUpdate();
		if (m_iLastSelection != m_iSelection)
		{
			m_iLastSelection = m_iSelection;
			m_xShadowParam.m_uFlags &= ~(ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW | ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS);

			switch (m_iSelection)
			{
			default:
				Asura_Assert(0, "Invalid shadow selection"); // fallthrough
			case 0:
				break;

			case 1:
				m_xShadowParam.m_uFlags |= ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW;
				break;

			case 2:
				m_xShadowParam.m_uFlags |= ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS;
				break;
			}
		}
	}

private:
	int m_iLastSelection;
	int m_iSelection;
	Asura_ShadowParameters& m_xShadowParam;

	static const char * const s_aszShadowModes[iNUM_SHADOW_MODES];
};

const char * const Tools_ShadowDropDown_List_Dialog::s_aszShadowModes[iNUM_SHADOW_MODES] = { "No Shadows", "Contribute To Averaged Shadow", "Cast Own Shadows" };


// Helper functions to enable/disable dialogues.
static bool CanScaleShadow(const Asura_ShadowParameters& xShadowParam)
{
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW) return true;
	return false;
}

static bool CanCastDarkeningShadow(const Asura_ShadowParameters& xShadowParam)
{
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS) return true;
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS) return true;
	return false;
}

static bool CanCastShadow(const Asura_ShadowParameters& xShadowParam)
{
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW) return true;
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS) return true;
	if (xShadowParam.m_uFlags & ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS) return true;
	return false;
}

///////////////////////////
// AddOverlayTab_Shadows //
///////////////////////////
// Add tabs for shadow.
static void AddOverlayTab_Shadows(Asura_Tabs_Dialog* pxTab, Asura_Vector_2& xOffset,
								 Asura_ShadowParameters& xShadowParam, bool* pbChanged)
{
	if (!pxTab) return;

	Asura_Dialog* pxDialog = 0;

	Asura_Callback_UserData<bool, const Asura_ShadowParameters&> xCanScaleShadow(CanScaleShadow, xShadowParam);
	Asura_Callback_UserData<bool, const Asura_ShadowParameters&> xCanCastShadow(CanCastShadow, xShadowParam);
	Asura_Callback_UserData<bool, const Asura_ShadowParameters&> xCanCastDarkeningShadow(CanCastDarkeningShadow, xShadowParam);

	pxDialog = new Tools_ShadowDropDown_List_Dialog(xOffset, xShadowParam, pbChanged);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Scale strength contribution by light brightness", xOffset, &xShadowParam.m_uFlags, ASURA_SHADOWFLAG_SCALE_STRENGTH_BY_BRIGHTNESS, pbChanged);
	pxDialog->SetIsEnabledFunction(xCanScaleShadow);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Shadows as darkening pass", xOffset, &xShadowParam.m_uFlags, ASURA_SHADOWFLAG_IS_DARKENING_PASS, pbChanged);
	pxDialog->SetIsEnabledFunction(xCanCastDarkeningShadow);
	pxTab->AddDialog(pxDialog);		
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Use noise to filter shadows", xOffset, &xShadowParam.m_uFlags, ASURA_SHADOWFLAG_USE_NOISE, pbChanged);
	pxDialog->SetIsEnabledFunction(xCanCastShadow);
	pxTab->AddDialog(pxDialog);		
	xOffset.y += Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_Numerical_Dialog( "Shadow Strength", xOffset, &xShadowParam.m_fShadowStrength, 0.0f, 4.0f, 0.1f, "%.03f", pbChanged);
	pxDialog->SetHelpText("For darkening pass shadows, including the averaged shadows: 0 = feint, 1 = strong");
	pxDialog->SetIsEnabledFunction(xCanCastShadow);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Softness Scale", xOffset, &xShadowParam.m_fShadowSoftness, -10.0f, 10.0f, 0.1f, "%.01f", pbChanged);		
	pxDialog->SetIsEnabledFunction(xCanCastShadow);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Vertical offset for casting shadows", xOffset, &xShadowParam.m_fVerticalOffset, -10.0f, 10.0f, 0.1f, "%.01f", pbChanged);
	pxDialog->SetHelpText("Adjust the shadow-casting position of the light, may help improve appearance in some glitchy cases");
	pxDialog->SetIsEnabledFunction(xCanCastShadow);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Cast Environment Shadows", xOffset, &xShadowParam.m_uFlags, ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS, pbChanged);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}


////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Projector::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;


	// Tabs dialogue
	Asura_Tabs_Dialog* pxTabsDialog = new Asura_Tabs_Dialog(xOffset, 0, 0);
	xNewOverlay.AddDialog(pxTabsDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	xNewOverlay.SetCurrentParentDialog(pxTabsDialog);

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

	pxTabsDialog->AddTab("Projector");
	AddOverlayTab_Projector(pxTabsDialog, xOffset);
	
	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	xOffset.y = fTabsStartY;

	pxTabsDialog->AddTab("Fall-off");
	AddOverlayTab_Falloff(pxTabsDialog, xOffset,
						  m_xParam.m_fFalloffInnerRatio, m_xParam.m_fFalloffPower,
						  m_xParam.m_fDistFadeStart, m_xParam.m_fDistFadeRange,
						  &s_bChangedEditOverlay);

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	xOffset.y = fTabsStartY;

	pxTabsDialog->AddTab("Shadows");
	AddOverlayTab_Shadows(pxTabsDialog, xOffset, m_xShadowParam, &s_bChangedEditOverlay);

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);

	// End of tabs
	xOffset.y = fTabsEndY + 1.0f;
	xNewOverlay.SetCurrentParentDialog(0);
	pxTabsDialog->SelectTab(0);

	// Remember which tab is open, when switching between different containers.
	static u_int s_uSelectedTab = 0;
	pxTabsDialog->SetSelectedTabPointer(&s_uSelectedTab);

	// Show frustrum
	pxDialog = new Asura_FlagTickbox_Dialog( "Show Frustum in Tools", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_TOOLS_RENDER_FRUSTRUM, &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText("Show frustum in Tools");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

// Helpers
static bool IsSpotlight(const Asura_Container_Projector_Param& xParam)
{
	return xParam.IsSpotLight();
}

static bool IsParallel(const Asura_Container_Projector_Param& xParam)
{
	return xParam.IsParallel();
}


/////////////////////////////
// AddOverlayTab_Projector //
/////////////////////////////
// Add tabs for projectors.
void Tools_Container_Projector::AddOverlayTab_Projector(Asura_Tabs_Dialog* pxTab, Asura_Vector_2& xOffset)
{
	if (!pxTab) return;

	const Asura_Vector_2 xSavedOffset(xOffset);
	Asura_Dialog* pxDialog = 0;
	
	// Textures
	pxDialog = new Asura_TexturePicker_Dialog("Texture", xOffset, NULL, &m_xParam.m_uTextureHash, &m_xTextureName,
											  fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );		

	xOffset.x += 15.0f;
	xOffset.y = xSavedOffset.y;

	pxDialog = new Asura_TexturePicker_Dialog("Detail", xOffset, NULL, &m_xParam.m_uDetailTextureHash, &m_xDetailTextureName,
											  fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );		

	xOffset = xSavedOffset;
	xOffset.y += 3.5f *  Asura_Dialog::LargeSpacing();

	// Colour
	Tools_ColourPicker_Dialog* pxColourPickerDialog =
					new Tools_ColourPicker_Dialog("Colour",	xOffset, Asura_Vector_2(3.0f, 1.2f),
												  &m_xParam.m_afRGB[0], &m_xParam.m_afRGB[1], &m_xParam.m_afRGB[2],
												  1.0f, &s_bChangedEditOverlay);

	pxColourPickerDialog->EnableHDR(true);
	pxTab->AddDialog( pxColourPickerDialog );		

	xOffset.x = pxColourPickerDialog->GetMaxCoords().x + 2.0f;
	pxDialog = new Asura_Dialog("Colour", xOffset);
	pxTab->AddDialog( pxDialog );

	xOffset.x = xSavedOffset.x;
	xOffset.y += 1.5f * Asura_Dialog::LargeSpacing();


	// Blend mode
	pxDialog = new Asura_DropDownList_Masked_Dialog(xOffset, g_aszProjectorBlendModes, m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_TYPE_MASK, uNUM_PROJECTOR_BLEND_MODES, uNUM_PROJECTOR_BLEND_MODES, 0, &s_bChangedEditOverlay, "Blend Mode");
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// FoV
	const Asura_Vector_2 xSavedOffset2(xOffset);
	pxDialog = new Asura_AngleNumerical_Dialog("FoV", xOffset, &m_xParam.m_fFoV, 0.0f, 150.0f, 0.1f, "%0.01f", &s_bChangedEditOverlay);
	pxDialog->SetHelpText("Field of view angle (degrees)");
	pxTab->AddDialog( pxDialog );	
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Aspect ratio
	pxDialog = new Asura_Numerical_Dialog("U:V Aspect Ratio", xOffset, &m_xParam.m_fUVAspect , 0.1f, 10.0f, 0.01f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog ); 
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
	// Near plane
	xOffset = xSavedOffset2;
	xOffset.x += fCOLUMN_OFFSET_X;
	pxDialog = new Asura_Numerical_Dialog("Near Plane Distance", xOffset, &m_xParam.m_fMinRange , 0.0f, 2500.0f, 0.01f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );	
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Far plane
	pxDialog = new Asura_Numerical_Dialog("Far Plane Distance", xOffset, &m_xParam.m_fMaxRange , 0.0f, 2500.0f, 0.01f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::LargeSpacing();
	xOffset.x = xSavedOffset2.x;


	// Parallel? - Scale (Parallel proj)
	pxDialog = new Asura_FlagTickbox_Dialog( "Parallel Projection", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_PARALLEL, &s_bChangedEditOverlay );
	pxTab->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Parallel scale
	Asura_Callback_UserData<bool, const Asura_Container_Projector_Param&> xIsParallel(IsParallel, m_xParam);
	pxDialog = new Asura_Numerical_Dialog("Parallel Proj Scale", xOffset, &m_xParam.m_fParallelScale , 0.1f, 2500.0f, 0.1f, "%0.02f", &s_bChangedEditOverlay);
	pxDialog->SetIsEnabledFunction(xIsParallel);
	pxTab->AddDialog( pxDialog ); 
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Detail texture scrolling
	pxDialog = new Asura_Dialog("UV Scrolling", xOffset);
	pxDialog->SetHelpText("UV Scrolling for the detail texture. If range=0 then scrolling will be continuous, else it will be sin() based");
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	const Asura_Vector_2 xSavedOffset3(xOffset);

	// U
	pxDialog = new Asura_Numerical_Dialog("U Speed", xOffset, &m_xParam.m_fUScrollSpeed , 0.0f, 99.0f, 0.01f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("U Range", xOffset, &m_xParam.m_fUScrollRange , 0.0f, 5.0f, 0.001f, "%0.03f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);				

	// V
	xOffset = xSavedOffset3;
	xOffset.x += fCOLUMN_OFFSET_X;

	pxDialog = new Asura_Numerical_Dialog("V Speed", xOffset, &m_xParam.m_fVScrollSpeed , 0.0f, 99.0f, 0.01f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("V Range", xOffset, &m_xParam.m_fVScrollRange , 0.0f, 5.0f, 0.001f, "%0.03f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	xOffset.x = xSavedOffset3.x;

	// Detail texture repeat
	pxDialog = new Asura_Numerical_Dialog("Detail Tex Repeats", xOffset, &m_xParam.m_fDetailTextureScale , 0.0f, 150.0f, 0.1f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );	
	xOffset.y += Asura_Dialog::LargeSpacing();


	// Limiting plane
	pxDialog = new Asura_FlagTickbox_Dialog( "Use limiting plane", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_USE_LIMITING_PLANE, &s_bChangedEditOverlay );
	pxDialog->SetHelpText("Use a limiting plane to restrict the region of the projection");
	pxTab->AddDialog( pxDialog );
	xOffset.x += fCOLUMN_OFFSET_X;

	pxDialog = new Asura_Function_Dialog("Edit Limiting Plane", xOffset, EditLimitingPlane, reinterpret_cast<int>(this));
	pxTab->AddDialog(pxDialog);
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Spotlight inner angle
	Asura_Callback_UserData<bool, const Asura_Container_Projector_Param&> xIsSpotlight(IsSpotlight, m_xParam);
	pxDialog = new Asura_AngleNumerical_Dialog("Spotlight Inner Angle", xOffset, &m_xParam.m_fInnerFoV, 0.0f, 150.0f, 0.1f, "%0.01f", &s_bChangedEditOverlay);
	pxDialog->SetIsEnabledFunction(xIsSpotlight);
	pxDialog->SetHelpText("Inner angle (in degrees) for spotlight. Must be less than the outer angle (which is set as the FoV).");
	pxTab->AddDialog( pxDialog ); 
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Use strobe
	pxDialog = new Asura_FlagTickbox_Dialog("Use Strobe", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_USE_STROBE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the projector will be affected by any active Strobe in the same collection.");
	pxTab->AddDialog(pxDialog);
	xOffset.x += fCOLUMN_OFFSET_X;

	// Use graph colour
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Colour", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_USE_GRAPH_COLOUR, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the projector will be affected by any active GraphColour, in the same collection, that has 'Colour' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Use graph flicker
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Flicker", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_USE_GRAPH_FLICKER, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the projector will be affected by any active GraphColour, in the same collection, that has 'Flicker' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x += fCOLUMN_OFFSET_X;

	// Use graph range
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Range", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PROJECTOR_FLAG_USE_GRAPH_RANGE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the projector will be affected by any active GraphColour, in the same collection, that has 'Range' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::DefaultSpacing();
}


///////////////////////
// EditLimitingPlane //
///////////////////////
void Tools_Container_Projector::EditLimitingPlane()
{
	if (!m_pxLimitingPlane)
	{
		m_pxLimitingPlane = new Tools_Container_LimitingPlane_Object();

		// Set orientation and position based on current collision plane.
		Asura_Maths::OrientationFromDirection(m_pxLimitingPlane->Orientation, m_xParam.m_xLimitingPlaneNormal);
		m_pxLimitingPlane->Position = m_xParam.m_xLimitingPlanePos;

		// Select new object.			
		Tools_Object_System::SelectObject(m_pxLimitingPlane);
	}
}

/////////////////
// ToolsRender //
/////////////////
// Update the limiting plane base position.
void Tools_Container_Projector::ToolsRender(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOri) const
{
	if (m_pxLimitingPlane)
	{
		m_pxLimitingPlane->SetBasePosOri(xPosition, xOri);
	}
}

///////////////////////////
// PerFrameOverlayUpdate //
///////////////////////////
void Tools_Container_Projector::PerFrameOverlayUpdate()
{
	// Workaround for a bug in the texture picker. When "escape" is pressed it clears the hash, but leaves the string alone.
	if (m_xParam.m_uTextureHash == ASURA_HASH_ID_UNSET && !m_xTextureName.IsBlank())
	{
		m_xParam.m_uTextureHash = Asura_GetHashID_IgnoreExtension(m_xTextureName.GetString());
	}

	if (m_xParam.m_uDetailTextureHash == ASURA_HASH_ID_UNSET && !m_xDetailTextureName.IsBlank())
	{
		m_xParam.m_uDetailTextureHash = Asura_GetHashID_IgnoreExtension(m_xDetailTextureName.GetString());
	}

	// Update from limiting plane object
	if (m_pxLimitingPlane)
	{
		if (m_pxLimitingPlane->m_bReadyToDelete)
		{
			delete m_pxLimitingPlane;
			m_pxLimitingPlane = 0;
		}
		else if (m_pxLimitingPlane->m_bGameEntitiesNeedUpdating)
		{
			Asura_Vector_3 xNormal(m_pxLimitingPlane->Orientation.e13, m_pxLimitingPlane->Orientation.e23, m_pxLimitingPlane->Orientation.e33);
			xNormal.Normalise();
			m_xParam.m_xLimitingPlaneNormal = m_pxLimitingPlane->GetBaseOri().ApplyTranspose(xNormal);
			
			Asura_Vector_3 xPos(m_pxLimitingPlane->Position - m_pxLimitingPlane->GetBasePos());
			m_xParam.m_xLimitingPlanePos = m_pxLimitingPlane->GetBaseOri().ApplyTranspose(xPos);

			// Reset flag
			m_pxLimitingPlane->m_bGameEntitiesNeedUpdating = false;

			// Ensure that the instance is updated.
			s_bChangedEditOverlay = true;
		}
	}

	// Enforce constraint.
	if (m_xParam.m_fInnerFoV > m_xParam.m_fFoV)
	{
		m_xParam.m_fInnerFoV = m_xParam.m_fFoV;
	}
}

///////////////////////////////////
// TraverseUsedTexturesForReport //
///////////////////////////////////
// Build a list of textures for texture report log.
void Tools_Container_Projector::TraverseUsedTexturesForReport(void (*pfnCallback)(int iTextureID, void* pUserData), void* pUserData) const
{
	const bool bSpotLight = m_xParam.IsSpotLight();
	if (!bSpotLight)
	{
		const int iTexID1 = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_uTextureHash );
		pfnCallback(iTexID1, pUserData);

		const int iTexID2 = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_uDetailTextureHash );
		pfnCallback(iTexID2, pUserData);
	}
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_Projector::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	const bool bSpotLight = m_xParam.IsSpotLight();
	if (!bSpotLight)
	{
		AddTextureToExportList(m_xParam.m_uTextureHash, m_xTextureName);
		AddTextureToExportList(m_xParam.m_uDetailTextureHash, m_xDetailTextureName);
	}
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_Projector::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	LoadRequiredTextureFile(m_xParam.m_uTextureHash, m_xTextureName);
	LoadRequiredTextureFile(m_xParam.m_uDetailTextureHash, m_xDetailTextureName);
}

////////////
// Verify //
////////////
void Tools_Container_Projector::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that the textures are loaded.
	LoadRequiredFiles();

	const bool bSpotLight = m_xParam.IsSpotLight();

	VerifyTexture(m_xParam.m_uTextureHash, m_xTextureName, "texture", !bSpotLight);
	VerifyTexture(m_xParam.m_uDetailTextureHash, m_xDetailTextureName, "detail texture", false);
}
#endif //#ifdef ASURA_TARGET_SM3


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_PhotonVolumeEdit
///////////////////////////////////////////////////////////////////////////////
#ifdef ASURA_TARGET_SM3
class Tools_Container_PhotonVolumeEdit : public Tools_VolumeEdit_Controller
{
public :
	Tools_Container_PhotonVolumeEdit(Tools_Container_Photon* pxToolsContainer)
		: Tools_VolumeEdit_Controller(&m_bModified)
		, m_pxToolsContainer(pxToolsContainer)
		, m_bInitialised(false)
		, m_bModified(false)
		, m_bNeedToDelete(false)
	{
		m_xBasePosition.Zero();
		m_xBaseOri.Identity();
		m_xBox = pxToolsContainer->GetParam().m_xLightVolume;
	}

	virtual void StartVolumeEditing()
	{
		Tools_VolumeEdit_Controller::StartVolumeEditing();
		if (GetNumberOfVolumes() == 0)
		{	
			CreateVolume();
		}
	}

	virtual void EndVolumeEditing()
	{
		Tools_VolumeEdit_Controller::EndVolumeEditing();
		m_bNeedToDelete = true;
	}

	void SetBasePosOri(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOri)
	{
		// Displace BB to base position if it has changed.
		if (xPosition != m_xBasePosition)
		{
			m_xBasePosition = xPosition;
			UpdateBBFromParam();
		}

		m_xBaseOri.FromMatrix(xOri);

		if (!m_bInitialised)
		{
			m_bInitialised = true;
			StartVolumeEditing();
		}
	}

	void UpdateBBFromParam()
	{
		m_xBox = m_pxToolsContainer->GetParam().m_xLightVolume;
		m_xBox.Displace(m_xBasePosition);
		m_bModified = false;
	}

	void UpdateParamFromBB()
	{
		m_pxToolsContainer->GetParam().m_xLightVolume = m_xBox;
		m_pxToolsContainer->GetParam().m_xLightVolume.Displace(-m_xBasePosition);
		m_bModified = false;
	}

	bool ShouldDelete() const { return m_bNeedToDelete; }
	bool IsModified() const { return m_bModified; }

	void SelectVolume() { Tools_VolumeEdit_Controller::SelectVolume(0); }
	
protected:
	// Don't allow >1 volume
	virtual bool CanCreateVolume() { return false; }
	virtual bool CanDeleteVolume() { return false; }

	// Gets the current number of volumes
	virtual int GetNumberOfVolumes() { return 1; }

	// Gets a pointer to the bounding box for a given volume
	virtual Asura_Bounding_Box* GetVolumeBox(int iIndex) { return &m_xBox; }

	// Gets a pointer to the orientation for a given volume. Returns 0 if there is none
	virtual Asura_Quat* GetVolumeOrient(int iIndex) { return &m_xBaseOri; }

	// Gets a default position for volume objects that have yet to gain a proper volume
	virtual Asura_Vector_3 GetDefaultPosition() { return m_xBasePosition; }

	// Gets the name of the owner, to use for part of the volume names
	virtual const char* GetName() { return m_pxToolsContainer->GetInstanceName(); }

	// Creates the new volume in the asura data
	virtual void CreateVolumeMain() {}

	// Deletes the volume from the asura data
	virtual void DeleteVolumeMain(int iIndex) {}

private :
	Tools_Container_Photon* m_pxToolsContainer;
	Asura_Vector_3 m_xBasePosition;
	Asura_Quat m_xBaseOri;
	Asura_Bounding_Box m_xBox;

	bool m_bInitialised;
	bool m_bModified;
	bool m_bNeedToDelete;
};
#endif //#ifdef ASURA_TARGET_SM3


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Photon
///////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_TARGET_SM3

Tools_Container_Photon::Tools_Container_Photon()
: m_pxLimitingVolume(0)
{
}

Tools_Container_Photon::~Tools_Container_Photon()
{
	if (m_pxLimitingVolume)
	{
		delete m_pxLimitingVolume;
		m_pxLimitingVolume = 0;
	}
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_PHOTON = 1;

void Tools_Container_Photon::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_PHOTON)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Photon", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);

	if (iVersion >= 1)
	{
		m_xShadowParam.ReadFromChunkStream(xStream);
	}

	xStream >> m_xSharpTextureName;
	xStream >> m_xBlurredTextureName;
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Photon::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_PHOTON;

	m_xParam.WriteToChunkStream(xStream);
	m_xShadowParam.WriteToChunkStream(xStream);

	xStream << m_xSharpTextureName;
	xStream << m_xBlurredTextureName;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Photon::CopyDataToAsura()
{
	Asura_Container_Photon* pxContainer = static_cast<Asura_Container_Photon*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_xParam = m_xParam;
		pxContainer->m_xShadowParam = m_xShadowParam;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Photon::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	// Tabs dialogue
	Asura_Tabs_Dialog* pxTabsDialog = new Asura_Tabs_Dialog(xOffset, 0, 0);
	xNewOverlay.AddDialog(pxTabsDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	xNewOverlay.SetCurrentParentDialog(pxTabsDialog);

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

	pxTabsDialog->AddTab("Photon");
	AddOverlayTab_Photon(pxTabsDialog, xOffset);
	
	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	xOffset.y = fTabsStartY;

	pxTabsDialog->AddTab("Fall-off");
	AddOverlayTab_Falloff(pxTabsDialog, xOffset,
						  m_xParam.m_fFalloffInnerRatio, m_xParam.m_fFalloffPower,
						  m_xParam.m_fDistFadeStart, m_xParam.m_fDistFadeRange,
						  &s_bChangedEditOverlay);

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	xOffset.y = fTabsStartY;

	pxTabsDialog->AddTab("Shadows");
	AddOverlayTab_Shadows(pxTabsDialog, xOffset, m_xShadowParam, &s_bChangedEditOverlay);

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);

	// End of tabs
	xOffset.y = fTabsEndY + 1.0f;
	xNewOverlay.SetCurrentParentDialog(0);
	pxTabsDialog->SelectTab(0);

	// Remember which tab is open, when switching between different containers.
	static u_int s_uSelectedTab = 0;
	pxTabsDialog->SetSelectedTabPointer(&s_uSelectedTab);

	// Show frustrum
	pxDialog = new Asura_FlagTickbox_Dialog( "Show Volume in Tools", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_TOOLS_RENDER_LIGHT_VOLUME, &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText("Show custom light volume in Tools");
	xNewOverlay.AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

// Helper
static bool IsUsingLimitingAngles(const Asura_Container_Photon_Param& xParam)
{
	return xParam.IsUsingLimitingAngles();
}

//////////////////////////
// AddOverlayTab_Photon //
//////////////////////////
// Add tabs for photons.
void Tools_Container_Photon::AddOverlayTab_Photon(Asura_Tabs_Dialog* pxTab, Asura_Vector_2& xOffset)
{
	if (!pxTab) return;

	const Asura_Vector_2 xSavedOffset(xOffset);
	Asura_Dialog* pxDialog = 0;

	// Textures
	pxDialog = new Asura_TexturePicker_Dialog("Projected Cubemap (sharp)", xOffset, NULL,
											  &m_xParam.m_uProjCubeTextureHash, &m_xSharpTextureName,
											  fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );		

	xOffset.x += 19.0f;
	xOffset.y = xSavedOffset.y;

	pxDialog = new Asura_TexturePicker_Dialog("Projected Cubemap (blurred)", xOffset, NULL,
											  &m_xParam.m_uProjCubeBlurredTextureHash, &m_xBlurredTextureName,
											  fTEXTURE_PICKER_SIZE, fTEXTURE_PICKER_SIZE, &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog );		

	xOffset = xSavedOffset;
	xOffset.y += 3.5f *  Asura_Dialog::LargeSpacing();
		

	// Colour
	Tools_ColourPicker_Dialog* pxColourPickerDialog =
					new Tools_ColourPicker_Dialog("Colour",	xOffset, Asura_Vector_2(3.0f, 1.2f),
												  &m_xParam.m_afRGB[0], &m_xParam.m_afRGB[1], &m_xParam.m_afRGB[2],
												  1.0f, &s_bChangedEditOverlay);

	pxColourPickerDialog->EnableHDR(true);
	pxTab->AddDialog( pxColourPickerDialog );

	xOffset.x = pxColourPickerDialog->GetMaxCoords().x + 2.0f;
	pxDialog = new Asura_Dialog("Colour", xOffset);
	pxTab->AddDialog( pxDialog );

	xOffset.x = xSavedOffset.x;
	xOffset.y += 1.5f * Asura_Dialog::LargeSpacing();

	// lerp
	pxDialog = new Asura_Numerical_Dialog("Sharp->Blurred blend distance", xOffset, &m_xParam.m_fProjCubeLerpDist, 0.0f, 250.0f, 0.1f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Range
	pxDialog = new Asura_Numerical_Dialog("Range", xOffset, &m_xParam.m_fPhotonRange, 0.0f, 5000.0f, 1.0f, "%0.02f", &s_bChangedEditOverlay);
	pxTab->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Limiting angles
	Asura_Callback_UserData<bool, const Asura_Container_Photon_Param&> xIsUsingLimitingAngles(IsUsingLimitingAngles, m_xParam);
	pxDialog = new Asura_FlagTickbox_Dialog("Use Limiting Angles", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_LIMITING_ANGLES, &s_bChangedEditOverlay);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_AngleNumerical_Dialog("Inner Angle", xOffset, &m_xParam.m_fLimitingAngleInner, 0.0f, 360.0f, 5.0f, "%0.02f", &s_bChangedEditOverlay);
	pxDialog->SetIsEnabledFunction(xIsUsingLimitingAngles);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_AngleNumerical_Dialog("Outer Angle", xOffset, &m_xParam.m_fLimitingAngleOuter, 0.0f, 360.0f, 5.0f, "%0.02f", &s_bChangedEditOverlay);
	pxDialog->SetIsEnabledFunction(xIsUsingLimitingAngles);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Dialog("(Spotlights are *much* more efficient for narrow beams)", xOffset);
	pxTab->AddDialog( pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Custom light volume
	pxDialog = new Asura_FlagTickbox_Dialog("Use Custom Light Volume", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_LIGHT_VOLUME, &s_bChangedEditOverlay);
	pxTab->AddDialog(pxDialog);
	
	xOffset.x += 23;
	pxDialog = new Asura_Function_Dialog("Edit Volume", xOffset, EditCustomLightVolume, reinterpret_cast<int>(this));
	pxTab->AddDialog(pxDialog);
	 
	xOffset.x += 12;
	pxDialog = new Asura_Function_Dialog("Reset Volume", xOffset, ResetCustomLightVolume, reinterpret_cast<int>(this), &s_bChangedEditOverlay);
	pxTab->AddDialog(pxDialog);
	xOffset.x -= 35;
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Use strobe
	pxDialog = new Asura_FlagTickbox_Dialog("Use Strobe", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_STROBE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the photon will be affected by any active Strobe in the same collection.");
	pxTab->AddDialog(pxDialog);
	xOffset.x += fCOLUMN_OFFSET_X;

	// Use graph colour
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Colour", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_GRAPH_COLOUR, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the photon will be affected by any active GraphColour, in the same collection, that has 'Colour' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Use graph flicker
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Flicker", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_GRAPH_FLICKER, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the photon will be affected by any active GraphColour, in the same collection, that has 'Flicker' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x += fCOLUMN_OFFSET_X;

	// Use graph range
	pxDialog = new Asura_FlagTickbox_Dialog("Use Graph Range", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_PHOTON_FLAG_USE_GRAPH_RANGE, &s_bChangedEditOverlay);
	pxDialog->SetHelpText("If ticked then the photon will be affected by any active GraphColour, in the same collection, that has 'Range' enabled.");
	pxTab->AddDialog(pxDialog);
	xOffset.x -= fCOLUMN_OFFSET_X;
	xOffset.y += Asura_Dialog::DefaultSpacing();

}

///////////////////////////
// EditCustomLightVolume //
///////////////////////////
void Tools_Container_Photon::EditCustomLightVolume()
{
	if (!m_pxLimitingVolume)
	{
		m_pxLimitingVolume = new Tools_Container_PhotonVolumeEdit(this);
	}
	else
	{
		m_pxLimitingVolume->SelectVolume();
	}
}

////////////////////////////
// ResetCustomLightVolume //
////////////////////////////
void Tools_Container_Photon::ResetCustomLightVolume()
{
	m_xParam.m_xLightVolume.Reset();
	m_xParam.m_xLightVolume.Set(Asura_Vector_3(0,0,0));
	m_xParam.m_xLightVolume.Grow(m_xParam.m_fPhotonRange);
	m_xParam.m_xLightVolumeOrient.Identity();

	if (m_pxLimitingVolume)
	{
		delete m_pxLimitingVolume;
		m_pxLimitingVolume = 0;
	}
}

/////////////////
// ToolsRender //
/////////////////
// Update the limiting plane base position.
void Tools_Container_Photon::ToolsRender(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOri) const
{
	if (m_pxLimitingVolume)
	{
		m_pxLimitingVolume->SetBasePosOri(xPosition, xOri);
	}
}

///////////////////////////
// PerFrameOverlayUpdate //
///////////////////////////
void Tools_Container_Photon::PerFrameOverlayUpdate()
{
	// Workaround for a bug in the texture picker. When "escape" is pressed it clears the hash, but leaves the string alone.
	if (m_xParam.m_uProjCubeTextureHash == ASURA_HASH_ID_UNSET && !m_xSharpTextureName.IsBlank())
	{
		m_xParam.m_uProjCubeTextureHash = Asura_GetHashID_IgnoreExtension(m_xSharpTextureName.GetString());
	}

	if (m_xParam.m_uProjCubeBlurredTextureHash == ASURA_HASH_ID_UNSET && !m_xBlurredTextureName.IsBlank())
	{
		m_xParam.m_uProjCubeBlurredTextureHash = Asura_GetHashID_IgnoreExtension(m_xBlurredTextureName.GetString());
	}

	if (m_pxLimitingVolume)
	{
		if (m_pxLimitingVolume->ShouldDelete())
		{
			delete m_pxLimitingVolume;
			m_pxLimitingVolume = 0;
		}
		else if (m_pxLimitingVolume->IsModified())
		{
			m_pxLimitingVolume->UpdateParamFromBB();
			
			// Ensure that the instance is updated.
			s_bChangedEditOverlay = true;
		}
	}
}

///////////////////////////////////
// TraverseUsedTexturesForReport //
///////////////////////////////////
// Build a list of textures for texture report log.
void Tools_Container_Photon::TraverseUsedTexturesForReport(void (*pfnCallback)(int iTextureID, void* pUserData), void* pUserData) const
{
	const int iTexID1 = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_uProjCubeTextureHash );
	pfnCallback(iTexID1, pUserData);

	const int iTexID2 = Asura_Texture_Management::GetIndexFromHashID( m_xParam.m_uProjCubeBlurredTextureHash );
	pfnCallback(iTexID2, pUserData);
}

//////////////////////////
// AddFilesToExportList //
//////////////////////////
void Tools_Container_Photon::AddFilesToExportList(List<Asura_String>& xNameList) const
{
	PARENT::AddFilesToExportList(xNameList);

	AddTextureToExportList(m_xParam.m_uProjCubeTextureHash, m_xSharpTextureName);
	AddTextureToExportList(m_xParam.m_uProjCubeBlurredTextureHash, m_xBlurredTextureName);
}

///////////////////////
// LoadRequiredFiles //
///////////////////////
void Tools_Container_Photon::LoadRequiredFiles() const
{
	PARENT::LoadRequiredFiles();

	LoadRequiredTextureFile(m_xParam.m_uProjCubeTextureHash, m_xSharpTextureName);
	LoadRequiredTextureFile(m_xParam.m_uProjCubeBlurredTextureHash, m_xBlurredTextureName);
}

////////////
// Verify //
////////////
void Tools_Container_Photon::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);

	// Make sure that the textures are loaded.
	LoadRequiredFiles();

	VerifyTexture(m_xParam.m_uProjCubeTextureHash, m_xSharpTextureName, "sharp texture", false);
	VerifyTexture(m_xParam.m_uProjCubeBlurredTextureHash, m_xBlurredTextureName, "blurred texture", false);
}
#endif //#ifdef ASURA_TARGET_SM3


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Strobe
///////////////////////////////////////////////////////////////////////////////
Tools_Container_Strobe::Tools_Container_Strobe()
: m_fMinTimeOff(1.0f)
, m_fMaxTimeOff(1.0f)
, m_fMinTimeOn(1.0f)
, m_fMaxTimeOn(1.0f)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_STROBE = 0;

void Tools_Container_Strobe::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_STROBE)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Strobe", iVersion));
		return;
	}

	xStream >> m_fMinTimeOff;
	xStream >> m_fMaxTimeOff;
	xStream >> m_fMinTimeOn;
	xStream >> m_fMaxTimeOn;
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Strobe::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_STROBE;

	xStream << m_fMinTimeOff;
	xStream << m_fMaxTimeOff;
	xStream << m_fMinTimeOn;
	xStream << m_fMaxTimeOn;

	PARENT::WriteToChunkStream(xStream);
}

void Tools_Container_Strobe::CopyDataToAsura()
{
	Asura_Container_Strobe* pxContainer = static_cast<Asura_Container_Strobe*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_fMinTimeOff = m_fMinTimeOff;
		pxContainer->m_fMaxTimeOff = m_fMaxTimeOff;
		pxContainer->m_fMinTimeOn  = m_fMinTimeOn;
		pxContainer->m_fMaxTimeOn  = m_fMaxTimeOn;
	}

	PARENT::CopyDataToAsura();
}

void Tools_Container_Strobe::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_MinMax_Dialog<float>("Time Off", xOffset, &m_fMinTimeOff, &m_fMaxTimeOff, 0, 100.0f, 0.01f, "%0.2f", &xNewOverlay, &s_bChangedEditOverlay_MinorChange);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_MinMax_Dialog<float>("Time On", xOffset, &m_fMinTimeOn, &m_fMaxTimeOn, 0, 100.0f, 0.01f, "%0.2f", &xNewOverlay, &s_bChangedEditOverlay_MinorChange);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_GraphColour
///////////////////////////////////////////////////////////////////////////////

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_GRAPHCOLOUR = 0;

void Tools_Container_GraphColour::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_GRAPHCOLOUR)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_GraphColour", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);
	
	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_GraphColour::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_GRAPHCOLOUR;

	// Not exporting, we want the spline data in Tools.
	m_xParam.WriteToChunkStream(xStream, false);

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_GraphColour::CopyDataToAsura()
{
	Asura_Container_GraphColour* pxContainer = static_cast<Asura_Container_GraphColour*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_xParam = m_xParam;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_GraphColour::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	static const char* const s_aszTabNames[] = { "Colour/Range", "Flicker" };

	Asura_Tabs_Dialog* pxTabs = new Asura_Tabs_Dialog( xOffset, s_aszTabNames, sizeof(s_aszTabNames) / sizeof(s_aszTabNames[0]) );
	xNewOverlay.AddDialog(pxTabs);
	xOffset.y += 1.2f * Asura_Dialog::LargeSpacing();

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

	Asura_Dialog* pxDialog = 0;

	// ----------------------------------------------------------
	// Colour/Range tab
	// ----------------------------------------------------------
	pxTabs->SelectTab(0);

	pxDialog = new Asura_Graph_Dialog<Asura_RGBA>("Colour/Range", xOffset, &m_xParam.m_xColourRangeGraph, 0.0f, 2.0f, &s_bChangedEditOverlay_MinorChange);
	pxTabs->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Enable graph colour (RGB)", xOffset, &m_xParam.m_uFlags,
											ASURA_CONTAINER_GRAPHCOLOUR_FLAG_ENABLE_GRAPH_COLOUR, &s_bChangedEditOverlay_MinorChange);
	pxTabs->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Enable graph range (A)", xOffset, &m_xParam.m_uFlags,
											ASURA_CONTAINER_GRAPHCOLOUR_FLAG_ENABLE_GRAPH_RANGE, &s_bChangedEditOverlay_MinorChange);
	pxTabs->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	xOffset.y = fTabsStartY;

	// ----------------------------------------------------------
	// Flicker sub-tab
	// ----------------------------------------------------------
	pxTabs->SelectTab(1);

	pxDialog = new Asura_Graph_Dialog<float>("Flicker", xOffset, &m_xParam.m_xFlickerGraph, 0.0f, 1.0f, &s_bChangedEditOverlay_MinorChange);
	pxTabs->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Enable graph flicker", xOffset, &m_xParam.m_uFlags,
											ASURA_CONTAINER_GRAPHCOLOUR_FLAG_ENABLE_GRAPH_FLICKER, &s_bChangedEditOverlay_MinorChange);
	pxTabs->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	fTabsEndY = Asura_Maths::Max(xOffset.y, fTabsEndY);
	
	// End of tabs
	xOffset.y = fTabsEndY + 1.0f;
	xNewOverlay.SetCurrentParentDialog(0);
	pxTabs->SelectTab(0);

	// Remember which tab is open, when switching between different containers.
	static u_int s_uSelectedTab = 0;
	pxTabs->SetSelectedTabPointer(&s_uSelectedTab);

	// Duration
	pxDialog = new Asura_Numerical_Dialog("Graph Duration", xOffset, &m_xParam.m_fGraphDuration, 0.0f, 999.0f, 1.0f, "%0.1f", &s_bChangedEditOverlay_MinorChange);
	pxDialog->SetHelpText("In seconds");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x += 22.0f;

		// Looping
	pxDialog = new Asura_FlagTickbox_Dialog("Looping", xOffset, &m_xParam.m_uFlags, ASURA_CONTAINER_GRAPHCOLOUR_FLAG_GRAPH_IS_LOOPED, &s_bChangedEditOverlay_MinorChange);
	pxDialog->SetHelpText("Looping will normally be used.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	xOffset.x -= 22.0f;
}

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Health
///////////////////////////////////////////////////////////////////////////////
Tools_Container_Health::Tools_Container_Health()
: m_fInitialHealth(100.0f)
, m_fMaxHealth(100.0f)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_HEALTH = 0;
	
void Tools_Container_Health::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_HEALTH)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Health", iVersion));
		return;
	}

	xStream >> m_fInitialHealth;
	xStream >> m_fMaxHealth;

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Health::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_HEALTH;

	xStream << m_fInitialHealth;
	xStream << m_fMaxHealth;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Health::CopyDataToAsura()
{
	Asura_Container_Health* pxContainer = static_cast<Asura_Container_Health*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_fInitialHealth = m_fInitialHealth;
		pxContainer->m_fMaxHealth = m_fMaxHealth;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Health::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	// Initial health
	pxDialog = new Asura_Numerical_Dialog("Initial health", xOffset, &m_fInitialHealth, 0.0f, 9999.0f, 1.0f, "%0.0f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Max health
	pxDialog = new Asura_Numerical_Dialog("Maximum health", xOffset, &m_fMaxHealth, 0.0f, 9999.0f, 1.0f, "%0.0f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

void Tools_Container_Health::PerFrameOverlayUpdate()
{
	if (m_fInitialHealth > m_fMaxHealth)
	{
		m_fInitialHealth = m_fMaxHealth;
	}
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Physics
///////////////////////////////////////////////////////////////////////////////

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_PHYSICS = 0;
	
void Tools_Container_Physics::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_PHYSICS)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Physics", iVersion));
		return;
	}

	m_xParam.ReadFromChunkStream(xStream);

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Physics::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_PHYSICS;

	m_xParam.WriteToChunkStream(xStream);

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Physics::CopyDataToAsura()
{
	Asura_Container_Physics* pxContainer = static_cast<Asura_Container_Physics*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_xParam = m_xParam;
	}

	PARENT::CopyDataToAsura();
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Physics::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_Numerical_Dialog( "Gravity Scale", xOffset, &m_xParam.m_fGravityScale, -10.0f, 10.0f, 0.100f, "%.3f", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "The gravity scale factor is how much the object is affected by gravity.\n1.0 means normal gravity. 0.0 means no gravity, 2.0 means double gravity, -1.0 means gravity is inverted and objects will float up, etc." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Elasticity", xOffset, &m_xParam.m_fElasticity, 0.0f, 1.0f, 0.010f, " %.3f ", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "Elasticity determines how much bounce objects have on collisions.\n1.0 means fully elastic (bounces away with same speed) and 0.0 means fully inelastic (stops dead)." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Friction", xOffset, &m_xParam.m_fFriction, 0.0f, 1000.0f, 0.100f, "%.3f", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "0.0 means no friction, and then you can have a number that increases meaning more friction." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Drag", xOffset, &m_xParam.m_fDrag, 0.0f, 1.000f, 0.010f, " %.3f ", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "Drag is how much air resistance there is to the object.\n0.0 means no drag, 1.0 means 100% drag and the object will lose any velocity it has pretty much instantaneously." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Angular Drag", xOffset, &m_xParam.m_fAngularDrag, 0.0f, 1.000f, 0.010f, " %.3f ", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "Drag is how much air resistance there is to the object.\n0.0 means no drag, 1.0 means 100% drag and the object will lose any angular velocity it has pretty much instantaneously." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog( "Mass", xOffset, &m_xParam.m_fMass, 0.100f, 100000.0f, 1.000f, "%.3f", &s_bChangedEditOverlay_MinorChange );
	pxDialog->SetHelpText( "How heavy the object is." );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Blueprint
///////////////////////////////////////////////////////////////////////////////

Tools_Container_Blueprint::Tools_Container_Blueprint()
: m_uBlueprintTypeHash(ASURA_HASH_ID_UNSET)
, m_uBlueprintHash(ASURA_HASH_ID_UNSET)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_BLUEPRINT = 0;
	
void Tools_Container_Blueprint::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_BLUEPRINT)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Blueprint", iVersion));
		return;
	}

	xStream >> m_uBlueprintTypeHash;
	xStream >> m_uBlueprintHash;

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Blueprint::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_BLUEPRINT;

	xStream << m_uBlueprintTypeHash;
	xStream << m_uBlueprintHash;

	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Blueprint::CopyDataToAsura()
{
	Asura_Container_Blueprint* pxContainer = static_cast<Asura_Container_Blueprint*>(m_pxAsuraContainer);
	Asura_Assert(pxContainer, "Null pointer, container not set.");
	if (pxContainer)
	{
		pxContainer->m_uBlueprintTypeHash = m_uBlueprintTypeHash;
		pxContainer->m_uBlueprintHash = m_uBlueprintHash;
	}

	PARENT::CopyDataToAsura();
}

/////////////////////
// SelectBlueprint //
/////////////////////
void Tools_Container_Blueprint::SelectBlueprint()
{
	Asura_Hash_ID uBlueprintTypeHash = m_uBlueprintTypeHash;
	Asura_Hash_ID uBlueprintHash = m_uBlueprintHash;
	
	BlueprintSelect_Overlay xBPOverlay( "Choose A Blueprint", &uBlueprintHash, &uBlueprintTypeHash );
	if ( !xBPOverlay.DoModal() ) { return; }

	m_uBlueprintTypeHash = uBlueprintTypeHash;
	m_uBlueprintHash	 = uBlueprintHash;

	RefreshDialogue();
	ResizeOverlay();
}

void Tools_Container_Blueprint::RefreshDialogue()
{
	// Set the type name string.
	m_xBlueprintTypeNameDialogue = "Type: ";
	if (m_uBlueprintTypeHash == ASURA_HASH_ID_UNSET)
	{
		m_xBlueprintTypeNameDialogue += "*None*";
	}
	else
	{
		const Asura_String* pxTypeName = Asura_Blueprint_System::GetTypeName(m_uBlueprintTypeHash);
		if (pxTypeName)
		{
			m_xBlueprintTypeNameDialogue += *pxTypeName;
		}
		else
		{
			m_xBlueprintTypeNameDialogue += "*Not found*";
		}
	}

	// Set the blueprint name.
	m_xBlueprintNameDialogue = "Blueprint: ";
	if (m_uBlueprintHash == ASURA_HASH_ID_UNSET)
	{	
		m_xBlueprintNameDialogue += "*None*";
	}
	else
	{
		Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID( m_uBlueprintTypeHash, m_uBlueprintHash );
		if (pxBlueprint)
		{
			//m_xBlueprintNameDialogue += pxBlueprint->GetNameIncludingAllParentNames();
			m_xBlueprintNameDialogue += pxBlueprint->GetName();
		}
		else
		{
			m_xBlueprintNameDialogue += "*Not found*";
		}
	}
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Blueprint::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog = 0;

	// Refresh the dynamic string labels.
	RefreshDialogue();

	pxDialog = new Asura_Function_Dialog("Select Blueprint", xOffset, SelectBlueprint, reinterpret_cast<int>(this), &s_bChangedEditOverlay_MinorChange);
	pxDialog->SetHelpText( "Select blueprint" );
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Dynamic_Dialog(&m_xBlueprintTypeNameDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Dynamic_Dialog(&m_xBlueprintNameDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM
