#include "../Gaia/RuGaia_DataStore.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuGaia_DataStore, CRuGaia_Base)
ruHEAP_DEFINE_SUBCLASS(CRuGaia_DataStore, "CRuGaia_DataStore", "CRuGaia_Base")
ruCLASSGUID_DEFINE(CRuGaia_DataStore, 0x0F0D4E07, 0x4DF34297, 0x94ED0023, 0xB7984461)

// ************************************************************************************************************************************************************

static const char *gaiaDSInternal_TableName_Header = "gaia_ds_header";
static const char *gaiaDSInternal_TableName_ZoneDescriptor = "gaia_ds_zone_descriptors";
static const char *gaiaDSInternal_TableName_ColObjDescriptor = "gaia_ds_colobj_descriptors";
static const char *gaiaDSInternal_TableName_ColObj = "gaia_ds_colobj_chunks";

// ************************************************************************************************************************************************************

CRuGaia_DataStore::Node::Node()
{
	m_colObjects = NULL;

	for(INT32 i = 0; i < 4; ++i)
	{
		m_children[i] = NULL;
	}
}

CRuGaia_DataStore::Node::Node(const CRuAABB &bounds, float maxExtents)
{
	m_center = (bounds.Maxima() + bounds.Minima()) / 2.0f;
	m_center.y = 0.0f;
	m_bounds = bounds;

	m_colObjects = NULL;

	// Must we initialize child nodes?
	if(bounds.Maxima().x - bounds.Minima().x > maxExtents || bounds.Maxima().z - bounds.Minima().z > maxExtents)
	{
		CRuAABB childBounds;

		for(INT32 i = 0; i < 4; ++i)
		{
			childBounds.SetBoundsToQuadrantXZ(bounds, i);
			m_children[i] = ruNEW Node(childBounds, maxExtents);
		}
	}
	else
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			m_children[i] = NULL;
		}
	}
}

CRuGaia_DataStore::Node::~Node()
{
	if(m_colObjects)
	{
		for(INT32 i = 0; i < m_colObjects->Count(); ++i)
		{
			delete m_colObjects->Get(i);
		}

		// Delete collision object descriptor list
		delete m_colObjects;
	}

	// Delete child nodes
	for(INT32 i = 0; i < 4; ++i)
	{
		delete m_children[i];
	}
}

BOOL CRuGaia_DataStore::Node::AddCollisionObject(IRuCollisionObject *colObject)
{
	// Is this node a leaf?
	if(IsLeaf())
	{
		if(m_colObjects == NULL)
		{
			m_colObjects = ruNEW CRuArrayList<ColObjectDesc *>();
		}

		// Create new object descriptor
		ColObjectDesc *objDesc = ruNEW ColObjectDesc();

		objDesc->m_dirty = TRUE;
		objDesc->m_key = colObject->GetCollisionObjectKey();
		objDesc->m_iteration = colObject->GetCollisionObjectIteration();
		objDesc->m_bounds = colObject->GetBounds();

		// Add object descriptor to list
		m_colObjects->Add(objDesc);

		// Update bounds
		m_bounds.Merge(colObject->GetBounds());

		return TRUE;
	}
	else
	{
		CRuVector3 colObjCenter = (colObject->GetBounds().Maxima() + colObject->GetBounds().Minima()) / 2.0f;
		colObjCenter.y = 0.0f;

		// Find nearest child
		INT32 nearestChild = 0;
		float nearestDistance = FLT_MAX;

		for(INT32 i = 0; i < 4; ++i)
		{
			float distance = (colObjCenter - m_children[i]->m_center).Magnitude();

			if(distance < nearestDistance)
			{
				nearestChild = i;
				nearestDistance = distance;
			}
		}

		// Add collision object to nearest child
		return m_children[nearestChild]->AddCollisionObject(colObject);
	}

	return FALSE;
}

BOOL CRuGaia_DataStore::Node::AddCollisionObject(ColObjectDesc *colObjectDesc)
{
	// Is this node a leaf?
	if(IsLeaf())
	{
		if(m_colObjects == NULL)
		{
			m_colObjects = ruNEW CRuArrayList<ColObjectDesc *>();
		}

		// Add object descriptor to list
		m_colObjects->Add(colObjectDesc);

		// Update bounds
		m_bounds.Merge(colObjectDesc->m_bounds);

		return TRUE;
	}
	else
	{
		CRuVector3 colObjCenter = (colObjectDesc->m_bounds.Maxima() + colObjectDesc->m_bounds.Minima()) / 2.0f;
		colObjCenter.y = 0.0f;

		// Find nearest child
		INT32 nearestChild = 0;
		float nearestDistance = FLT_MAX;

		for(INT32 i = 0; i < 4; ++i)
		{
			float distance = (colObjCenter - m_children[i]->m_center).Magnitude();

			if(distance < nearestDistance)
			{
				nearestChild = i;
				nearestDistance = distance;
			}
		}

		// Add collision object to nearest child
		return m_children[nearestChild]->AddCollisionObject(colObjectDesc);
	}

	return FALSE;
}

CRuGaia_DataStore::ColObjectDesc *CRuGaia_DataStore::Node::GetCollisionObject(INT32 objKey)
{
	BOOL childRemoved = FALSE;

	if(m_colObjects)
	{
		// Search for a collision object with the matching key
		for(INT32 i = 0; i < m_colObjects->Count(); ++i)
		{
			if(m_colObjects->Get(i)->m_key == objKey)
			{
				return m_colObjects->Get(i);
			}
		}
	}

	// Recurse to child nodes
	if(IsLeaf() == FALSE)
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			ColObjectDesc *colObjDesc = m_children[i]->GetCollisionObject(objKey);

			if(colObjDesc)
			{
				return colObjDesc;
			}
		}
	}

	return NULL;
}

BOOL CRuGaia_DataStore::Node::RemoveCollisionObject(INT32 objKey)
{
	BOOL childRemoved = FALSE;

	if(m_colObjects)
	{
		// Search for a collision object with the matching key
		for(INT32 i = 0; i < m_colObjects->Count(); ++i)
		{
			if(m_colObjects->Get(i)->m_key == objKey)
			{
				// Remove object
				m_colObjects->RemoveAt(i);
				i--;

				// Set child removed to true
				childRemoved |= TRUE;
			}
		}
	}

	// Recurse to child nodes
	if(IsLeaf() == FALSE)
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			childRemoved |= m_children[i]->RemoveCollisionObject(objKey);
		}
	}

	return childRemoved;
}

void CRuGaia_DataStore::Node::ResetDirty()
{
	if(m_colObjects)
	{
		for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
		{
			m_colObjects->Get(i)->m_dirty = FALSE;
		}
	}

	// Recurse to children
	if(!IsLeaf())
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			m_children[i]->ResetDirty();
		}
	}
}

void CRuGaia_DataStore::Node::Enumerate(CRuCollisionQuery *colQuery, CRuArrayList<ColObjectDesc *> &objListOut)
{
	BOOL recurseToChildren = FALSE;

	switch(colQuery->m_queryType)
	{
		case ruCOLQUERY_RAY:
		case ruCOLQUERY_RAY_NEAREST:
		case ruCOLQUERY_RAY_TRIPICK:
			{
				CRuCollisionQuery_Ray_Base *rayQuery = static_cast<CRuCollisionQuery_Ray_Base *>(colQuery);

				if(Intersects(rayQuery->m_colRay, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(rayQuery->m_colRay, m_colObjects->Get(i)->m_bounds))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_SEGMENT:
		case ruCOLQUERY_SEGMENT_HITTEST:
		case ruCOLQUERY_SEGMENT_NEAREST:
			{
				CRuCollisionQuery_Segment_Base *segQuery = static_cast<CRuCollisionQuery_Segment_Base *>(colQuery);

				if(Intersects(segQuery->m_colSegment, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(segQuery->m_colSegment, m_colObjects->Get(i)->m_bounds))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		// AABB queries
		case ruCOLQUERY_AABB_HITTEST:
		case ruCOLQUERY_AABB_SUBOBJECT:
		case ruCOLQUERY_GAIA_CAMTEST:
		case ruCOLQUERY_USER_FLAG_SET_AABB:
			{
				CRuCollisionQuery_AABB_Base *subObjQuery = static_cast<CRuCollisionQuery_AABB_Base *>(colQuery);

				if(Intersects(subObjQuery->m_colAABB, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(subObjQuery->m_colAABB, m_colObjects->Get(i)->m_bounds))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_FRUSTUM_SUBOBJECT:
			{
				CRuCollisionQuery_Frustum_SubObject *subObjQuery = static_cast<CRuCollisionQuery_Frustum_SubObject *>(colQuery);

				if(Intersects(subObjQuery->m_colFrustumBounds, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(IntersectsFrustum(subObjQuery->m_colFrustum, m_colObjects->Get(i)->m_bounds))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_SPHERE_SLIDETEST:
		case ruCOLQUERY_SPHERE_ANCHOR:
			{
				CRuCollisionQuery_Sphere_Base *sphereQuery = static_cast<CRuCollisionQuery_Sphere_Base *>(colQuery);

				if(Intersects(m_bounds, sphereQuery->m_colSphere))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(m_colObjects->Get(i)->m_bounds, sphereQuery->m_colSphere))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_ENUMERATE:
			{
			}

			break;

		case ruCOLQUERY_USER_FLAG_SET_RAY:
			{
				CRuCollisionQuery_User_Flag_Set_Ray *rayQuery = static_cast<CRuCollisionQuery_User_Flag_Set_Ray *>(colQuery);

				if(Intersects(rayQuery->m_markingRay, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(rayQuery->m_markingRay, m_colObjects->Get(i)->m_bounds))
								{
									// Mark object as dirty and add it to list of colliding objects
									m_colObjects->Get(i)->m_dirty = TRUE;
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_USER_FLAG_SET_SPHERE:
			{
				CRuCollisionQuery_User_Flag_Set_Sphere *setQuery = static_cast<CRuCollisionQuery_User_Flag_Set_Sphere *>(colQuery);

				if(Intersects(m_bounds, setQuery->m_markingSphere))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(m_colObjects->Get(i)->m_bounds, setQuery->m_markingSphere))
								{
									// Mark object as dirty and add it to list of colliding objects
									m_colObjects->Get(i)->m_dirty = TRUE;
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;

		case ruCOLQUERY_USER_FLAG_GET_SEGMENT:
			{
				CRuCollisionQuery_User_Flag_Get_Segment *segQuery = static_cast<CRuCollisionQuery_User_Flag_Get_Segment *>(colQuery);

				if(Intersects(segQuery->m_colSegment, m_bounds))
				{
					if(IsLeaf())
					{
						if(m_colObjects)
						{
							for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
							{
								if(Intersects(segQuery->m_colSegment, m_colObjects->Get(i)->m_bounds))
								{
									objListOut.Add(m_colObjects->Get(i));
								}
							}
						}
					}
					else
					{
						recurseToChildren = TRUE;
					}
				}
			}

			break;
	}

	// Recurse to children
	if(recurseToChildren)
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			m_children[i]->Enumerate(colQuery, objListOut);
		}
	}
}

void CRuGaia_DataStore::Node::EnumerateDirty(CRuArrayList<ColObjectDesc *> &objListOut)
{
	if(m_colObjects)
	{
		for(INT32 i = 0, numObjs = m_colObjects->Count(); i < numObjs; ++i)
		{
			if(m_colObjects->Get(i)->m_dirty)
			{
				objListOut.Add(m_colObjects->Get(i));
			}
		}
	}

	// Recurse to children
	if(!IsLeaf())
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			m_children[i]->EnumerateDirty(objListOut);
		}
	}
}

// ************************************************************************************************************************************************************

CRuGaia_DataStore::CRuGaia_DataStore()
:	m_dataStore(NULL),
	m_pathHierarchy(NULL),
	m_srcResourceName(NULL),
	m_root(NULL)
{
	// Initialize collision object queue
	m_colObjQueue = ruNEW CRuLRUQueue<INT32, IRuCollisionObject *>(128, 2048);

	// Initialize stream compression components
	m_streamCompressor = ruNEW CRuStreamCompressor();
	m_compressedChunkStream = ruNEW CRuMemoryStream();
	m_decompressedChunkStream = ruNEW CRuMemoryStream();

	// Pre-initialize temporary stream buffer
	m_tempStream.SetBufferSize(4 * 1024 * 1024);
}

CRuGaia_DataStore::~CRuGaia_DataStore()
{
	// Release data store
	ruSAFE_RELEASE(m_dataStore);

	// Release path hierarchy object
	ruSAFE_RELEASE(m_pathHierarchy);

	// Flush collision object queue
	FlushColObjQueue();

	ClearZoneDescriptors();
	ClearDeletedZoneDescriptors();

	delete[] m_srcResourceName;

	delete m_root;

	// Destroy collision object queue
	delete m_colObjQueue;

	// Release stream compression components
	delete m_streamCompressor;
	delete m_compressedChunkStream;
	delete m_decompressedChunkStream;
}

BOOL CRuGaia_DataStore::Create(CRuDataStore_Base *dataStore, char *srcResourceName, const CRuAABB &bounds, float maxExtents, float gridSpacing)
{
	// Store reference to data store
	m_dataStore = dataStore;
	m_dataStore->AddRef();

	// Create tables
	m_tableHandle_Header = m_dataStore->CreateTable(gaiaDSInternal_TableName_Header, 4);
	m_tableHandle_ZoneDescriptor = m_dataStore->CreateTable(gaiaDSInternal_TableName_ZoneDescriptor, 12);
	m_tableHandle_ColObjDescriptor = m_dataStore->CreateTable(gaiaDSInternal_TableName_ColObjDescriptor, 4);
	m_tableHandle_ColObj = m_dataStore->CreateTable(gaiaDSInternal_TableName_ColObj, 4);

	// Save bounds and grid spacing
	m_bounds = bounds;
	m_maxExtents = maxExtents;
	m_gridSpacing = gridSpacing;

	// Save source resource name
	INT32 srcResNameLength = strlen(srcResourceName) + 1;
	m_srcResourceName = ruNEW char [srcResNameLength];
	memcpy(m_srcResourceName, srcResourceName, srcResNameLength);

	// Reset temporary stream
	m_tempStream.SetStreamSize(0);

	// Write version
	const INT32 version = 1;
	m_tempStream.Write(&version, sizeof(INT32));

	// Write resource name length, including NULL
	m_tempStream.Write(&srcResNameLength, sizeof(INT32));

	// Write resource name, including NULL
	m_tempStream.Write(m_srcResourceName, srcResNameLength);

	// Write maximal bounds
	m_tempStream.Write(&m_bounds.Minima().x, sizeof(float));
	m_tempStream.Write(&m_bounds.Minima().y, sizeof(float));
	m_tempStream.Write(&m_bounds.Minima().z, sizeof(float));
	m_tempStream.Write(&m_bounds.Maxima().x, sizeof(float));
	m_tempStream.Write(&m_bounds.Maxima().y, sizeof(float));
	m_tempStream.Write(&m_bounds.Maxima().z, sizeof(float));

	// Write max extents
	m_tempStream.Write(&m_maxExtents, sizeof(float));

	// Write grid spacing
	m_tempStream.Write(&m_gridSpacing, sizeof(float));

	// Store header entry
	INT32 dummyKey = 0;
	m_dataStore->Store(m_tableHandle_Header, reinterpret_cast<BYTE *>(&dummyKey), &m_tempStream);

	// Initialize empty tree
	m_root = ruNEW Node(m_bounds, m_maxExtents);

	// Initialize path hierarchy
	m_pathHierarchy = ruNEW CRuPathHierarchy_DS();
	m_pathHierarchy->Bind(m_dataStore, m_bounds);

	return TRUE;
}

BOOL CRuGaia_DataStore::Open(CRuDataStore_Base *dataStore)
{
	// Store reference to data store
	m_dataStore = dataStore;
	m_dataStore->AddRef();

	// Get table handles
	m_tableHandle_Header = m_dataStore->GetTableHandle(gaiaDSInternal_TableName_Header);
	m_tableHandle_ZoneDescriptor = m_dataStore->GetTableHandle(gaiaDSInternal_TableName_ZoneDescriptor);
	m_tableHandle_ColObjDescriptor = m_dataStore->GetTableHandle(gaiaDSInternal_TableName_ColObjDescriptor);
	m_tableHandle_ColObj = m_dataStore->GetTableHandle(gaiaDSInternal_TableName_ColObj);

	// Abort if we are unable to find the table handle
	if(m_tableHandle_Header < 0)
	{
		ruSAFE_RELEASE(m_dataStore);
		return FALSE;
	}

	// Retrieve header entry
	INT32 dummyKey = 0;
	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Header, reinterpret_cast<BYTE *>(&dummyKey));

	if(stream == NULL)
	{
		return FALSE;
	}

	// Write version
	INT32 version;
	stream->Read(&version, sizeof(INT32));

	// Read resource name length, including NULL
	INT32 srcResNameLength = 0;
	stream->Read(&srcResNameLength, sizeof(INT32));

	// Read resource name, including NULL
	if(srcResNameLength > 0)
	{
		m_srcResourceName = ruNEW char [srcResNameLength];
		stream->Read(m_srcResourceName, srcResNameLength);
	}
	else
	{
		m_srcResourceName = ruNEW char [1];
		m_srcResourceName[0] = NULL;
	}

	// Read maximal bounds
	stream->Read(&m_bounds.Minima().x, sizeof(float));
	stream->Read(&m_bounds.Minima().y, sizeof(float));
	stream->Read(&m_bounds.Minima().z, sizeof(float));
	stream->Read(&m_bounds.Maxima().x, sizeof(float));
	stream->Read(&m_bounds.Maxima().y, sizeof(float));
	stream->Read(&m_bounds.Maxima().z, sizeof(float));

	// Read max extents
	stream->Read(&m_maxExtents, sizeof(float));

	// Read grid spacing
	stream->Read(&m_gridSpacing, sizeof(float));

	delete stream;

	// Initialize empty tree
	m_root = ruNEW Node(m_bounds, m_maxExtents);

	// Load collision descriptors
	LoadCollisionObjectDescriptors();

	// Load time groups
	LoadTimeGroups();

	// Initialize path hierarchy
	m_pathHierarchy = ruNEW CRuPathHierarchy_DS();
	m_pathHierarchy->Bind(m_dataStore, m_bounds);

	return TRUE;
}

BOOL CRuGaia_DataStore::CopyTo(CRuDataStore_Base *targetDataStore)
{
	if(m_dataStore)
	{
		INT32 srcTableList[4];
		srcTableList[0] = m_tableHandle_Header;
		srcTableList[1] = m_tableHandle_ZoneDescriptor;
		srcTableList[2] = m_tableHandle_ColObjDescriptor;
		srcTableList[3] = m_tableHandle_ColObj;

		// Create tables
		INT32 targetTableList[4];
		targetTableList[0] = targetDataStore->CreateTable(gaiaDSInternal_TableName_Header, 4);
		targetTableList[1] = targetDataStore->CreateTable(gaiaDSInternal_TableName_ZoneDescriptor, 12);
		targetTableList[2] = targetDataStore->CreateTable(gaiaDSInternal_TableName_ColObjDescriptor, 4);
		targetTableList[3] = targetDataStore->CreateTable(gaiaDSInternal_TableName_ColObj, 4);

		// Copy data in each table
		for(INT32 i = 0; i < 4; ++i)
		{
			INT32 numEntries = m_dataStore->GetNumRecords(srcTableList[i]);
			INT32 keyLength = m_dataStore->GetTableKeyLength(srcTableList[i]);
			BYTE *key = ruNEW BYTE [keyLength];

			for(INT32 j = 0; j < numEntries; ++j)
			{
				// Retrieve key and data
				IRuStream *stream = m_dataStore->RetrieveByIndex(srcTableList[i], j);

				if(stream)
				{
					m_dataStore->RetrieveKeyByIndex(srcTableList[i], j, key);
					targetDataStore->Store(targetTableList[i], key, stream);
					delete stream;
				}
			}
			
			delete[] key;
		}

		m_pathHierarchy->CopyTo(targetDataStore);

		return TRUE;
	}

	return TRUE;
}

BOOL CRuGaia_DataStore::Save()
{
	// Save collision object descriptors
	SaveCollisionObjectDescriptors();

	// Save collision objects
	SaveCollisionObjects();

	// Save time groups
	SaveTimeGroups();

	// Save path data
	m_pathHierarchy->Save();

	return FALSE;
}

const CRuAABB &CRuGaia_DataStore::GetBounds() const
{
	return m_bounds;
}

float CRuGaia_DataStore::GetGridSpacing() const
{
	return m_gridSpacing;
}

const char *CRuGaia_DataStore::GetSourceResourceName() const
{
	return m_srcResourceName;
}

INT32 CRuGaia_DataStore::GetNumZoneDescriptors() const
{
	return m_zoneDescriptors.Count();
}

const CRuGaia_ZoneDescriptor *CRuGaia_DataStore::GetZoneDescriptor(INT32 id, INT32 timeGroup, INT32 weatherGroup) const
{
	// Search for a descriptor with matching time group, weather group, and zone ID
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		// TODO: Put in extra search criteria for when exact match cannot be found
		if(	m_zoneDescriptors[i].m_zoneDescriptor->m_id == id &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_timeGroup == timeGroup &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_weatherGroup == weatherGroup)
		{
			return m_zoneDescriptors[i].m_zoneDescriptor;
		}
	}

	return NULL;
}

const CRuGaia_ZoneDescriptor *CRuGaia_DataStore::GetZoneDescriptorByIndex(INT32 index) const
{
	return m_zoneDescriptors[index].m_zoneDescriptor;
}

BOOL CRuGaia_DataStore::SetZoneDescriptor(const CRuGaia_ZoneDescriptor *zoneDesc)
{
	// Search for a descriptor with matching time group, weather group, and zone ID
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(	m_zoneDescriptors[i].m_zoneDescriptor->m_id == zoneDesc->m_id &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_timeGroup == zoneDesc->m_timeGroup &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_weatherGroup == zoneDesc->m_weatherGroup)
		{
			// Copy members
			m_zoneDescriptors[i].m_dirty = TRUE;
			*(m_zoneDescriptors[i].m_zoneDescriptor) = *zoneDesc;

			return TRUE;
		}
	}

	// Make a copy of the descriptor and add it to the descriptor table
	INT32 index = m_zoneDescriptors.Count();
	m_zoneDescriptors.Add(ZoneDescriptorInfo());
	m_zoneDescriptors[index].m_zoneDescriptor = ruNEW CRuGaia_ZoneDescriptor();
	m_zoneDescriptors[index].m_dirty = TRUE;
	*(m_zoneDescriptors[index].m_zoneDescriptor) = *zoneDesc;

	return TRUE;
}

BOOL CRuGaia_DataStore::RemoveZoneDescriptor(const CRuGaia_ZoneDescriptor *zoneDesc)
{
	// Search for a descriptor with matching time group, weather group, and zone ID
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(	m_zoneDescriptors[i].m_zoneDescriptor->m_id == zoneDesc->m_id &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_timeGroup == zoneDesc->m_timeGroup &&
			m_zoneDescriptors[i].m_zoneDescriptor->m_weatherGroup == zoneDesc->m_weatherGroup)
		{
			m_deletedZoneDescriptors.Add(m_zoneDescriptors[i]);
			m_zoneDescriptors.RemoveAt(i);

			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuGaia_DataStore::RemoveZoneDescriptorAtIndex(INT32 index)
{
	if(index >= 0 && index < m_zoneDescriptors.Count())
	{
		m_deletedZoneDescriptors.Add(m_zoneDescriptors[index]);
		m_zoneDescriptors.RemoveAt(index);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuGaia_DataStore::SetZoneName(INT32 id, const char *zoneName)
{
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(m_zoneDescriptors[i].m_zoneDescriptor->m_id == id)
		{
			m_zoneDescriptors[i].m_zoneDescriptor->SetName(zoneName);
			m_zoneDescriptors[i].m_dirty = TRUE;
		}
	}

	return TRUE;
}

BOOL CRuGaia_DataStore::SetZoneID(INT32 oldID, INT32 newID)
{
	if(oldID == newID)
	{
		return FALSE;
	}

	// Make sure newID does not exist
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(m_zoneDescriptors[i].m_zoneDescriptor->m_id == newID)
		{
			return FALSE;
		}
	}

	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(m_zoneDescriptors[i].m_zoneDescriptor->m_id == oldID)
		{
			// Make a copy of the descriptor, change its ID
			INT32 index = m_zoneDescriptors.Count();
			m_zoneDescriptors.Add(ZoneDescriptorInfo());
			m_zoneDescriptors[index].m_zoneDescriptor = ruNEW CRuGaia_ZoneDescriptor();
			m_zoneDescriptors[index].m_dirty = TRUE;
			*(m_zoneDescriptors[index].m_zoneDescriptor) = *m_zoneDescriptors[i].m_zoneDescriptor;
			m_zoneDescriptors[index].m_zoneDescriptor->m_id = newID;
		}
	}

	// Remove old descriptors
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		if(m_zoneDescriptors[i].m_zoneDescriptor->m_id == oldID)
		{
			m_deletedZoneDescriptors.Add(m_zoneDescriptors[i]);
			m_zoneDescriptors.RemoveAt(i);
			--i;
		}
	}

	return TRUE;
}

IRuPathHierarchy_Base *CRuGaia_DataStore::GetPathHierarchy()
{
	return m_pathHierarchy;
}

BOOL CRuGaia_DataStore::AddCollisionObject(IRuCollisionObject *colObject)
{
	// Search the uncommitted collision object list and release the old collision object if it is found
	for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
	{
		if(m_uncommittedCollisionObjects[i]->GetCollisionObjectKey() == colObject->GetCollisionObjectKey())
		{
			m_uncommittedCollisionObjects[i]->Release();
			m_uncommittedCollisionObjects.RemoveAt(i);
			break;
		}
	}

	// Add new collision object to the uncommitted collision object list
	colObject->AddRef();
	m_uncommittedCollisionObjects.Add(colObject);

	// Remove the collision object from the instance queue if it exists
	INT32 objKey = colObject->GetCollisionObjectKey();
	DWORD keyHash = RuGlobalHashGen().GetHashCode(static_cast<UINT32>(objKey));
	m_colObjQueue->Remove(objKey, keyHash);

	// Remove collision object descriptor from the object tree if it already exists
	m_root->RemoveCollisionObject(objKey);

	// Add collision object to the object tree
	return m_root->AddCollisionObject(colObject);
}

IRuCollisionObject *CRuGaia_DataStore::GetCollisionObject(INT32 objKey)
{
	// Search the uncommitted collision object list and return it if it is found
	for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
	{
		if(m_uncommittedCollisionObjects[i]->GetCollisionObjectKey() == objKey)
		{
			return m_uncommittedCollisionObjects[i];
		}
	}

	// Search for the specified object descriptor
	ColObjectDesc *colObjDesc = m_root->GetCollisionObject(objKey);

	// Return the instanced collision object
	if(colObjDesc)
	{
		return InstanceCollisionObject(colObjDesc);
	}

	return NULL;
}

BOOL CRuGaia_DataStore::RemoveCollisionObject(INT32 objKey)
{
	// Search the uncommitted collision object list and release the deleted collision object if it is found
	for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
	{
		if(m_uncommittedCollisionObjects[i]->GetCollisionObjectKey() == objKey)
		{
			m_uncommittedCollisionObjects[i]->Release();
			m_uncommittedCollisionObjects.RemoveAt(i);
			break;
		}
	}

	// Add the object key to the list of deleted keys
	m_deletedCollisionObjectDescriptors.Add(objKey);

	// Remove the collision object from the instance queue if it exists
	DWORD keyHash = RuGlobalHashGen().GetHashCode(static_cast<UINT32>(objKey));
	m_colObjQueue->Remove(objKey, keyHash);

	// Remove collision object from the object tree
	return m_root->RemoveCollisionObject(objKey);
}

BOOL CRuGaia_DataStore::QueryCollision(CRuCollisionQuery *colQuery)
{
	if(m_root)
	{
		m_objList.Clear();

		// Enumerate all collision objects involved in this query
		m_root->Enumerate(colQuery, m_objList);

		// Perform collision query on all of the enumerated collision objects
		for(INT32 i = 0; i < m_objList.Count(); ++i)
		{
			// Instance the collision object
			IRuCollisionObject *colObject = InstanceCollisionObject(m_objList[i]);

			if(colObject)
			{
				if(colQuery->m_queryType == ruCOLQUERY_AABB_SUBOBJECT)
				{
					static_cast<CRuCollisionQuery_AABB_SubObject *>(colQuery)->m_colObjects->Add(colObject);
				}
				else if(colQuery->m_queryType == ruCOLQUERY_FRUSTUM_SUBOBJECT)
				{
					static_cast<CRuCollisionQuery_Frustum_SubObject *>(colQuery)->m_colObjects->Add(colObject);
				}
				else
				{
					// If the collision query is one that changes flags on the object, put the object in the uncommitted queue
					if(colQuery->m_queryType == ruCOLQUERY_USER_FLAG_SET_RAY || colQuery->m_queryType == ruCOLQUERY_USER_FLAG_SET_AABB || colQuery->m_queryType == ruCOLQUERY_USER_FLAG_SET_SPHERE)
					{
						BOOL alreadyCommitted = FALSE;

						// Search the uncommitted collision object list and make sure it is not already committed to the list
						for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
						{
							if(m_uncommittedCollisionObjects[i]->GetCollisionObjectKey() == colObject->GetCollisionObjectKey())
							{
								alreadyCommitted = TRUE;
								break;
							}
						}

						// If the collision object is not already committed, commit it
						if(alreadyCommitted == FALSE)
						{
							colObject->AddRef();
							m_uncommittedCollisionObjects.Add(colObject);
						}
					}

					// Perform collision query
					colObject->QueryCollision(colQuery);
				}
			}
		}

		return TRUE;
	}

	return FALSE;
}

void CRuGaia_DataStore::ClearZoneDescriptors()
{
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		delete m_zoneDescriptors[i].m_zoneDescriptor;
	}

	m_zoneDescriptors.Clear();
}

void CRuGaia_DataStore::ClearDeletedZoneDescriptors()
{
	for(INT32 i = 0; i < m_deletedZoneDescriptors.Count(); ++i)
	{
		delete m_deletedZoneDescriptors[i].m_zoneDescriptor;
	}

	m_deletedZoneDescriptors.Clear();
}

void CRuGaia_DataStore::SaveCollisionObjectDescriptors()
{
	// Iterate through deleted collision object descriptors and delete them
	for(INT32 i = 0; i < m_deletedCollisionObjectDescriptors.Count(); ++i)
	{
		// Delete collision object descriptor
		m_dataStore->Delete(m_tableHandle_ColObjDescriptor, reinterpret_cast<BYTE *>(&m_deletedCollisionObjectDescriptors[i]));

		// Delete collision object
		m_dataStore->Delete(m_tableHandle_ColObj, reinterpret_cast<BYTE *>(&m_deletedCollisionObjectDescriptors[i]));
	}

	// Enumerate dirty descriptors
	m_objList.Clear();
	m_root->EnumerateDirty(m_objList);

	// Iterate through dirty descriptors and save them
	for(INT32 i = 0; i < m_objList.Count(); ++i)
	{
		// Reset temporary stream size
		m_tempStream.SetStreamSize(0);

		// Fill stream with descriptor data
		m_tempStream.Write(&m_objList[i]->m_key, sizeof(INT32));
		m_tempStream.Write(&m_objList[i]->m_iteration, sizeof(INT32));
		m_tempStream.Write(&m_objList[i]->m_bounds.Minima().x, sizeof(REAL));
		m_tempStream.Write(&m_objList[i]->m_bounds.Minima().y, sizeof(REAL));
		m_tempStream.Write(&m_objList[i]->m_bounds.Minima().z, sizeof(REAL));
		m_tempStream.Write(&m_objList[i]->m_bounds.Maxima().x, sizeof(REAL));
		m_tempStream.Write(&m_objList[i]->m_bounds.Maxima().y, sizeof(REAL));
		m_tempStream.Write(&m_objList[i]->m_bounds.Maxima().z, sizeof(REAL));

		// Store descriptor
		m_dataStore->Store(m_tableHandle_ColObjDescriptor, reinterpret_cast<BYTE *>(&m_objList[i]->m_key), &m_tempStream);
	}

	// Clear
	m_root->ResetDirty();
	m_deletedCollisionObjectDescriptors.Clear();
}

void CRuGaia_DataStore::LoadCollisionObjectDescriptors()
{
	// Get number of descriptors
	INT32 numDescriptors = m_dataStore->GetNumRecords(m_tableHandle_ColObjDescriptor);

	// Iterate through each descriptor
	for(INT32 i = 0; i < numDescriptors; ++i)
	{
		// Retrieve descriptor record
		IRuStream *stream = m_dataStore->RetrieveByIndex(m_tableHandle_ColObjDescriptor, i);

		if(stream == NULL)
			continue;

		// Create new object descriptor
		ColObjectDesc *objDesc = ruNEW ColObjectDesc();

		objDesc->m_dirty = FALSE;

		// Read descriptor data
		stream->Read(&objDesc->m_key, sizeof(INT32));
		stream->Read(&objDesc->m_iteration, sizeof(INT32));
		stream->Read(&objDesc->m_bounds.Minima().x, sizeof(REAL));
		stream->Read(&objDesc->m_bounds.Minima().y, sizeof(REAL));
		stream->Read(&objDesc->m_bounds.Minima().z, sizeof(REAL));
		stream->Read(&objDesc->m_bounds.Maxima().x, sizeof(REAL));
		stream->Read(&objDesc->m_bounds.Maxima().y, sizeof(REAL));
		stream->Read(&objDesc->m_bounds.Maxima().z, sizeof(REAL));

		// Add object descriptor to the descriptor tree
		m_root->AddCollisionObject(objDesc);

		delete stream;
	}
}

void CRuGaia_DataStore::SaveCollisionObjects()
{
	for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
	{
		// Serialize collision object into memory stream
		m_tempStream.SetStreamSize(0);
		m_uncommittedCollisionObjects[i]->SerializeTo(&m_tempStream);

		// Compress memory stream
		m_compressedChunkStream->SetStreamSize(0);
		m_streamCompressor->Compress(&m_tempStream, m_compressedChunkStream);

		// Store descriptor
		INT32 key = m_uncommittedCollisionObjects[i]->GetCollisionObjectKey();
		m_dataStore->Store(m_tableHandle_ColObj, reinterpret_cast<BYTE *>(&key), m_compressedChunkStream);

		// Free the collision object
		m_uncommittedCollisionObjects[i]->Release();
	}

	// Clear list of uncommitted objects
	m_uncommittedCollisionObjects.Clear();
}

void CRuGaia_DataStore::SaveTimeGroups()
{
	// Iterate through deleted zone descriptors
	for(INT32 i = 0; i < m_deletedZoneDescriptors.Count(); ++i)
	{
		// Reset output stream size
		m_tempStream.SetStreamSize(0);

		// Stream identification key
		m_tempStream.Write(&m_deletedZoneDescriptors[i].m_zoneDescriptor->m_id, sizeof(INT32));
		m_tempStream.Write(&m_deletedZoneDescriptors[i].m_zoneDescriptor->m_timeGroup, sizeof(INT32));
		m_tempStream.Write(&m_deletedZoneDescriptors[i].m_zoneDescriptor->m_weatherGroup, sizeof(INT32));

		// Copy the zone descriptor key
		BYTE zoneDescriptorKey[12];
		memcpy(zoneDescriptorKey, m_tempStream.GetBuffer(), 12);

		// Delete zone descriptor
		m_dataStore->Delete(m_tableHandle_ZoneDescriptor, zoneDescriptorKey);
	}

	// Iterate through each zone descriptor looking for dirty ones
	for(INT32 i = 0; i < m_zoneDescriptors.Count(); ++i)
	{
		// Skip if the descriptor is not dirty
		if(m_zoneDescriptors[i].m_dirty == FALSE)
			continue;

		// Reset dirty flag
		m_zoneDescriptors[i].m_dirty = FALSE;

		// Reset output stream size
		m_tempStream.SetStreamSize(0);

		// Write signature
		INT32 signature = 0xFFFFFFFF;
		m_tempStream.Write(&signature, sizeof(INT32));

		// Write version
		INT32 version = 2;
		m_tempStream.Write(&version, sizeof(INT32));

		// Stream identification key
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_id, sizeof(INT32));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_timeGroup, sizeof(INT32));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_weatherGroup, sizeof(INT32));

		// Write length of zone name including NULL
		INT32 zoneNameLength = m_zoneDescriptors[i].m_zoneDescriptor->m_name ? strlen(m_zoneDescriptors[i].m_zoneDescriptor->m_name) + 1 : 0;
		m_tempStream.Write(&zoneNameLength, sizeof(INT32));

		// Write zone name
		if(zoneNameLength > 0)
		{
			m_tempStream.Write(m_zoneDescriptors[i].m_zoneDescriptor->m_name, sizeof(char) * zoneNameLength);
		}

		// Colors
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_color_Primary, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_color_Secondary, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_color_Ambient, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_color_Specular, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_color_Fog, sizeof(DWORD));

		// Fog
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_fog_Start, sizeof(float));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_fog_End, sizeof(float));

		// Light setup
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_theta_Standard, sizeof(float));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_phi_Standard, sizeof(float));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_theta_Specular, sizeof(float));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_phi_Specular, sizeof(float));

		// Cloud setup
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_cloud_HorizonColor, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_cloud_OverheadColor, sizeof(DWORD));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_cloud_HorizonCount, sizeof(INT32));
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_cloud_OverheadCount, sizeof(INT32));

		// Write length of sky gradient texture name including NULL
		INT32 skyGradientTextureNameLength = m_zoneDescriptors[i].m_zoneDescriptor->m_skyGradientTextureName ? strlen(m_zoneDescriptors[i].m_zoneDescriptor->m_skyGradientTextureName) + 1 : 0;
		m_tempStream.Write(&skyGradientTextureNameLength, sizeof(INT32));

		// Write sky gradient texture name
		if(skyGradientTextureNameLength > 0)
		{
			m_tempStream.Write(m_zoneDescriptors[i].m_zoneDescriptor->m_skyGradientTextureName, sizeof(char) * skyGradientTextureNameLength);
		}

		// Write length of horizon cloud texture name including NULL
		INT32 horizonCloudTextureNameLength = m_zoneDescriptors[i].m_zoneDescriptor->m_horizonCloudTextureName ? strlen(m_zoneDescriptors[i].m_zoneDescriptor->m_horizonCloudTextureName) + 1 : 0;
		m_tempStream.Write(&horizonCloudTextureNameLength, sizeof(INT32));

		// Write horizon cloud texture name
		if(horizonCloudTextureNameLength > 0)
		{
			m_tempStream.Write(m_zoneDescriptors[i].m_zoneDescriptor->m_horizonCloudTextureName, sizeof(char) * horizonCloudTextureNameLength);
		}

		// Write length of overhead cloud texture name including NULL
		INT32 overheadCloudTextureNameLength = m_zoneDescriptors[i].m_zoneDescriptor->m_overheadCloudTextureName ? strlen(m_zoneDescriptors[i].m_zoneDescriptor->m_overheadCloudTextureName) + 1 : 0;
		m_tempStream.Write(&overheadCloudTextureNameLength, sizeof(INT32));

		// Write overhead cloud texture name
		if(overheadCloudTextureNameLength > 0)
		{
			m_tempStream.Write(m_zoneDescriptors[i].m_zoneDescriptor->m_overheadCloudTextureName, sizeof(char) * overheadCloudTextureNameLength);
		}

		// Write length of overhead cloud texture name including NULL
		INT32 customSkyBoxNameLength = m_zoneDescriptors[i].m_zoneDescriptor->m_customSkyBoxName ? strlen(m_zoneDescriptors[i].m_zoneDescriptor->m_customSkyBoxName) + 1 : 0;
		m_tempStream.Write(&customSkyBoxNameLength, sizeof(INT32));

		// Write overhead cloud texture name
		if(customSkyBoxNameLength > 0)
		{
			m_tempStream.Write(m_zoneDescriptors[i].m_zoneDescriptor->m_customSkyBoxName, sizeof(char) * customSkyBoxNameLength);
		}

		// Write frame rate multiplier
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_frameRateMultiplier, sizeof(REAL));

		// Write cloud mode
		m_tempStream.Write(&m_zoneDescriptors[i].m_zoneDescriptor->m_cloudMode, sizeof(DWORD));

		// Copy the zone descriptor key
		BYTE zoneDescriptorKey[12];
		memcpy(zoneDescriptorKey, &m_tempStream.GetBuffer()[8], 12);

		// Store zone descriptor
		m_dataStore->Store(m_tableHandle_ZoneDescriptor, zoneDescriptorKey, &m_tempStream);
	}

	// Flush deleted descriptors list
	ClearDeletedZoneDescriptors();
}

void CRuGaia_DataStore::LoadTimeGroups()
{
	// Get number of zone descriptors
	INT32 numZoneDescriptors = m_dataStore->GetNumRecords(m_tableHandle_ZoneDescriptor);

	// Iterate through each zone descriptor
	for(INT32 i = 0; i < numZoneDescriptors; ++i)
	{
		// Retrieve zone descriptor record
		IRuStream *stream = m_dataStore->RetrieveByIndex(m_tableHandle_ZoneDescriptor, i);

		if(stream == NULL)
			continue;

		// Create new zone descriptor
		INT32 j = m_zoneDescriptors.Count();
		m_zoneDescriptors.Add(ZoneDescriptorInfo());
		m_zoneDescriptors[j].m_zoneDescriptor = ruNEW CRuGaia_ZoneDescriptor();
		m_zoneDescriptors[j].m_dirty = FALSE;

		// Read signature and version
		INT32 signature;
		INT32 version = 1;
		stream->Read(&signature, sizeof(INT32));

		if(signature == 0xFFFFFFFF)
		{
			// Signature found, read version 
			stream->Read(&version, sizeof(INT32));
		}
		else
		{
			// No signature, this is an old model stream, so rewind
			stream->Seek(-4, ruSSM_Current);
		}

		switch(version)
		{
			case 1:
				{
					// Read zone ID
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_id, sizeof(INT32));

					// Read time group
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_timeGroup, sizeof(INT32));

					// Read weather group
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_weatherGroup, sizeof(INT32));

					// Error trapping
					if(m_zoneDescriptors[j].m_zoneDescriptor->m_id < 0 || m_zoneDescriptors[j].m_zoneDescriptor->m_timeGroup < 0 || m_zoneDescriptors[j].m_zoneDescriptor->m_weatherGroup < 0)
					{
						m_zoneDescriptors.RemoveAt(j);
						continue;
					}

					// Read length of zone name including NULL
					INT32 zoneNameLength;
					stream->Read(&zoneNameLength, sizeof(INT32));

					// Read zone name
					if(zoneNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_name = ruNEW char [zoneNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_name, sizeof(char) * zoneNameLength);
					}

					// Read primary color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Primary, sizeof(DWORD));

					// Read secondary color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Secondary, sizeof(DWORD));

					// Read ambient color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Ambient, sizeof(DWORD));

					// Read specular color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Specular, sizeof(DWORD));

					// Read fog color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Fog, sizeof(DWORD));

					// Read fog start
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_fog_Start, sizeof(float));

					// Read fog end
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_fog_End, sizeof(float));

					// Read theta
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_theta_Standard, sizeof(float));

					// Read phi
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_phi_Standard, sizeof(float));

					// Read theta
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_theta_Specular, sizeof(float));

					// Read phi
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_phi_Specular, sizeof(float));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_HorizonColor, sizeof(DWORD));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_OverheadColor, sizeof(DWORD));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_HorizonCount, sizeof(INT32));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_OverheadCount, sizeof(INT32));

					// Read length of sky gradient texture name including NULL
					INT32 skyGradientTextureNameLength;
					stream->Read(&skyGradientTextureNameLength, sizeof(INT32));

					// Read sky gradient texture name
					if(skyGradientTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_skyGradientTextureName = ruNEW char [skyGradientTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_skyGradientTextureName, sizeof(char) * skyGradientTextureNameLength);
					}

					// Read length of horizon cloud texture name including NULL
					INT32 horizonCloudTextureNameLength;
					stream->Read(&horizonCloudTextureNameLength, sizeof(INT32));

					// Read horizon cloud texture name
					if(horizonCloudTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_horizonCloudTextureName = ruNEW char [horizonCloudTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_horizonCloudTextureName, sizeof(char) * horizonCloudTextureNameLength);
					}

					// Read length of overhead cloud texture name including NULL
					INT32 overheadCloudTextureNameLength;
					stream->Read(&overheadCloudTextureNameLength, sizeof(INT32));

					// Read overhead cloud texture name
					if(overheadCloudTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_overheadCloudTextureName = ruNEW char [overheadCloudTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_overheadCloudTextureName, sizeof(char) * overheadCloudTextureNameLength);
					}

					// Read cloud mode
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloudMode, sizeof(DWORD));
				}

				break;

			case 2:
				{
					// Read zone ID
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_id, sizeof(INT32));

					// Read time group
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_timeGroup, sizeof(INT32));

					// Read weather group
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_weatherGroup, sizeof(INT32));

					// Error trapping
					if(m_zoneDescriptors[j].m_zoneDescriptor->m_id < 0 || m_zoneDescriptors[j].m_zoneDescriptor->m_timeGroup < 0 || m_zoneDescriptors[j].m_zoneDescriptor->m_weatherGroup < 0)
					{
						m_zoneDescriptors.RemoveAt(j);
						continue;
					}

					// Read length of zone name including NULL
					INT32 zoneNameLength;
					stream->Read(&zoneNameLength, sizeof(INT32));

					// Read zone name
					if(zoneNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_name = ruNEW char [zoneNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_name, sizeof(char) * zoneNameLength);
					}

					// Read primary color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Primary, sizeof(DWORD));

					// Read secondary color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Secondary, sizeof(DWORD));

					// Read ambient color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Ambient, sizeof(DWORD));

					// Read specular color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Specular, sizeof(DWORD));

					// Read fog color
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_color_Fog, sizeof(DWORD));

					// Read fog start
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_fog_Start, sizeof(float));

					// Read fog end
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_fog_End, sizeof(float));

					// Read theta
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_theta_Standard, sizeof(float));

					// Read phi
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_phi_Standard, sizeof(float));

					// Read theta
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_theta_Specular, sizeof(float));

					// Read phi
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_phi_Specular, sizeof(float));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_HorizonColor, sizeof(DWORD));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_OverheadColor, sizeof(DWORD));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_HorizonCount, sizeof(INT32));

					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloud_OverheadCount, sizeof(INT32));

					// Read length of sky gradient texture name including NULL
					INT32 skyGradientTextureNameLength;
					stream->Read(&skyGradientTextureNameLength, sizeof(INT32));

					// Read sky gradient texture name
					if(skyGradientTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_skyGradientTextureName = ruNEW char [skyGradientTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_skyGradientTextureName, sizeof(char) * skyGradientTextureNameLength);
					}

					// Read length of horizon cloud texture name including NULL
					INT32 horizonCloudTextureNameLength;
					stream->Read(&horizonCloudTextureNameLength, sizeof(INT32));

					// Read horizon cloud texture name
					if(horizonCloudTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_horizonCloudTextureName = ruNEW char [horizonCloudTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_horizonCloudTextureName, sizeof(char) * horizonCloudTextureNameLength);
					}

					// Read length of overhead cloud texture name including NULL
					INT32 overheadCloudTextureNameLength;
					stream->Read(&overheadCloudTextureNameLength, sizeof(INT32));

					// Read overhead cloud texture name
					if(overheadCloudTextureNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_overheadCloudTextureName = ruNEW char [overheadCloudTextureNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_overheadCloudTextureName, sizeof(char) * overheadCloudTextureNameLength);
					}

					// Read length of custom sky box name including NULL
					INT32 customSkyBoxNameLength;
					stream->Read(&customSkyBoxNameLength, sizeof(INT32));

					// Read custom sky box name
					if(customSkyBoxNameLength > 0)
					{
						m_zoneDescriptors[j].m_zoneDescriptor->m_customSkyBoxName = ruNEW char [customSkyBoxNameLength];
						stream->Read(m_zoneDescriptors[j].m_zoneDescriptor->m_customSkyBoxName, sizeof(char) * customSkyBoxNameLength);
					}

					// Write frame rate multiplier
					stream->Read(&m_zoneDescriptors[i].m_zoneDescriptor->m_frameRateMultiplier, sizeof(REAL));

					// Read cloud mode
					stream->Read(&m_zoneDescriptors[j].m_zoneDescriptor->m_cloudMode, sizeof(DWORD));
				}

				break;
		}

		delete stream;
	}
}

IRuCollisionObject *CRuGaia_DataStore::InstanceCollisionObject(ColObjectDesc *objDesc)
{
	DWORD keyHash = RuGlobalHashGen().GetHashCode(static_cast<UINT32>(objDesc->m_key));
	IRuCollisionObject *colObject = NULL;

	// Locate the collision key
	if(m_colObjQueue->Get(objDesc->m_key, keyHash, colObject) == FALSE)
	{
		// Reclaim space
		IRuCollisionObject *reclaimedColObject;
		while(m_colObjQueue->Reclaim(objDesc->m_key, keyHash, 1, reclaimedColObject))
		{
			// Release
			if(reclaimedColObject)
			{
				reclaimedColObject->Release();
			}
		}

		// Search the uncommitted collision object list
		for(INT32 i = 0; i < m_uncommittedCollisionObjects.Count(); ++i)
		{
			if(m_uncommittedCollisionObjects[i]->GetCollisionObjectKey() == objDesc->m_key)
			{
				colObject = m_uncommittedCollisionObjects[i];
				colObject->AddRef();
				break;
			}
		}

		// Attempt to serialize from the database
		if(colObject == NULL)
		{
			IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_ColObj, reinterpret_cast<BYTE *>(&objDesc->m_key));

			if(stream)
			{
				// Decompress chunk data block
				m_streamCompressor->Decompress(stream, 0, stream->GetStreamSize(), m_decompressedChunkStream);

				IRuObject *obj = g_ruObjectFactory.CreateObject(m_decompressedChunkStream);
				if(obj && obj->GetType().IsSubClassOf(IRuCollisionObject::Type))
				{
					colObject = static_cast<IRuCollisionObject *>(obj);
					colObject->SetCollisionObjectKey(objDesc->m_key);
					colObject->SetCollisionObjectIteration(objDesc->m_iteration);
				}
			}

			delete stream;
		}

		if(colObject)
		{
			// Add the collision object to the queue
			m_colObjQueue->Add(objDesc->m_key, keyHash, colObject, 1);
		}
	}

	return colObject;
}

BOOL CRuGaia_DataStore::FlushColObjQueue()
{
	if(m_colObjQueue)
	{
		// Iterate through all instanced entities
		m_colObjQueue->ForAllElements(FlushColObjQueueCallback, this);

		// Clear instanced entity queue
		m_colObjQueue->Clear();
	}

	return TRUE;
}

BOOL CRuGaia_DataStore::FlushColObjQueueCallback(const void *key, const void *data, void *userData)
{
	IRuCollisionObject *colObj = *((IRuCollisionObject **) data);

	if(colObj)
	{
		colObj->Release();
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

#pragma managed
