#include "../../Direct3D/Engine/RuEngineD3D.h"
#include "../../Engine/Texture/RuTextureAtlas_Paged.h"

#include "../../Scene/Base/RuSceneBase.h"

#pragma unmanaged

IRuTexture **causticTex = NULL;
INT32 curCausticIndex = 0;

extern char g_ruGlobalMessage[2048];

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

CRuRendererOptions::CRuRendererOptions()
{
	m_shadow_Enable = FALSE;
	m_shadow_CasterLevel = 1;
	m_shadow_ReceiverLevel = 1;
	m_shadow_Fade_Start = 400.0f;
	m_shadow_Fade_End = 700.0f;
	m_shadow_Cutoff = 700.0f;
	m_shadow_Strength = 0.25f;

	m_terrain_TextureDistance = 1000.0f;
	m_terrain_Alternate = TRUE;
}

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

IRuSceneRenderer::~IRuSceneRenderer()
{
}

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

CRuSceneRenderer::CRuSceneRenderer()
:	m_instanceManager(NULL),
	m_camera(NULL),
	m_nullTexture(NULL),
	m_cubicFilterLUT(NULL),
	m_auxDepthStencilSurface(NULL),
	m_ru2D_Viewport_Width(0.0f),
	m_ru2D_Viewport_Height(0.0f),
	m_ru2D_TextureAtlas(NULL),
	m_ru2D_MaxPrimitiveCount(8192),
	m_ru2D_PrimitiveCount(0),
	m_ru2D_Mesh(NULL),
	m_ru2D_Material(NULL),
	m_ru3D_MaxPrimitiveCount(8192),
	m_ru3D_PrimitiveCount(0),
	m_lightTree(NULL),
	m_numPrimitivesRendered(0),
	m_numBatchesIssued(0),
	m_perfProfileStream(NULL),
	m_query(NULL)
{
	// Initialize variables required by 2D toolkit
	m_ru2D_Mesh = ruNEW CRuDynamicMesh();
	m_ru2D_Mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, m_ru2D_MaxPrimitiveCount * 3, m_ru2D_MaxPrimitiveCount, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE);
	m_ru2D_Mesh->AddTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);

	m_ru2D_Material = ruNEW CRuMaterial();

	// Initialize variables required by 3D toolkit
	m_ru3D_Mesh = ruNEW CRuDynamicMesh();
	m_ru3D_Mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, m_ru3D_MaxPrimitiveCount * 3, m_ru3D_MaxPrimitiveCount, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE);
	m_ru3D_Mesh->AddTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);

	m_ru3D_Material = ruNEW CRuMaterial();

	// Create the light tree
	m_lightTree = ruNEW CRuAABBTree();

	// Create skinning cache
	m_skinningCache = ruNEW CRuLRUQueue<UINT64, CachedSkin>(512, 2048);

	// Create 2D toolkit texture atlas
	m_ru2D_DXT5AtlasTexture = g_ruResourceManager->CreateTexture("ruSCENERENDERER_ATLAS_DXT5", 2048, 2048, 1, ruSURFACEFMT_DXT5);

	if(m_ru2D_DXT5AtlasTexture)
	{
		CRuTextureAtlas_Paged *dxt5Atlas = ruNEW CRuTextureAtlas_Paged();
		dxt5Atlas->Initialize(m_ru2D_DXT5AtlasTexture, 256, 256, 1024, 1024);

		m_ru2D_TextureAtlas = ruNEW CRuTextureAtlas_Aggregate();
		m_ru2D_TextureAtlas->BindSubAtlas(dxt5Atlas);

		ruSAFE_RELEASE(dxt5Atlas);
	}

	// Setup the default parameter cache
	m_defaultParamCache.ClearParameterCache();

	// Set default parameters
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GLOBAL_ALPHA, RuCastFloatToDWORD(1.0f));
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GLOBAL_DIFFUSE, 0xFFFFFFFF);

	m_defaultParamCache.CacheParameter(ruPARAMTYPE_LIGHT_SELF, RuCastFloatToDWORD(0.0f));

	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GEOMETRY_2SIDED, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GEOMETRY_FACETED, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GEOMETRY_WIRE, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GEOMETRY_ALPHATESTENABLE, TRUE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_GEOMETRY_ALPHATESTVALUE, 150);

	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_ALPHAMODE, ruALPHAMODE_NORMAL);

	// Set default fog
	REAL fogStart = 100.0f, fogEnd = 1500.0f;
	RUCOLOR fogColor = RUCOLOR_ARGB(255, 100, 100, 100);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_FOG_START, RuCastFloatToDWORD(fogStart));
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_FOG_END, RuCastFloatToDWORD(fogEnd));
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_FOG_COLOR, (DWORD) fogColor);

/*
	SH9 Constants
	-------------

	normalisation = PI * 16 / 17
	const1 = 0.282095^2 * normalisation * 1
	const2 = 0.488603^2 * normalisation * (2 / 3)
	const3 = 1.092548^2 * normalisation * (1 / 4)
	const4 = 0.315392^2 * normalisation * (1 / 4)
	const5 = 0.546274^2 * normalisation * (1 / 4)
*/

	// Pre-calculate 3rd-order spherical harmonic constants
	REAL normalization = PI * 16.0f / 17.0f;
	m_sh9_Const1 = 0.282095f * 0.282095f * normalization * 1.0f;
	m_sh9_Const2 = 0.488603f * 0.488603f * normalization * (2.0f /3.0f);
	m_sh9_Const3 = 1.092548f * 1.092548f * normalization * (1.0f /4.0f);
	m_sh9_Const4 = 0.315392f * 0.315392f * normalization * (1.0f /4.0f);
	m_sh9_Const5 = 0.546274f * 0.546274f * normalization * (1.0f /4.0f);

	m_altCamera = NULL;
	m_shadowCamera = NULL;
	m_shadowMaterial = NULL;
	m_shadowMapMaterial = NULL;
}

CRuSceneRenderer::~CRuSceneRenderer()
{
	// Flush the skinning cache
	FlushSkinningCache();

	// Release the skinning cache
	delete m_skinningCache;

	// Release the NULL texture
	ruSAFE_RELEASE(m_nullTexture);

	// Release the cubic filter look-up texture
	ruSAFE_RELEASE(m_cubicFilterLUT);

	// Release the auxiliary depth stencil surface
	ruSAFE_RELEASE(m_auxDepthStencilSurface);

	// Release resources used by the 2D toolkit
	if(m_ru2D_Mesh)
		m_ru2D_Mesh->Release();

	if(m_ru2D_Material)
		m_ru2D_Material->Release();

	// Release resources used by the 3D toolkit
	if(m_ru3D_Mesh)
		m_ru3D_Mesh->Release();

	if(m_ru3D_Material)
		m_ru3D_Material->Release();

	ruSAFE_RELEASE(m_ru2D_DXT5AtlasTexture);

	// Release 2d toolkit texture atlas
	ruSAFE_RELEASE(m_ru2D_TextureAtlas);

	// Release matrix stack
	while(m_usedMatrixStack.IsEmpty() == FALSE)
	{
		m_unusedMatrixStack.Push(m_usedMatrixStack.Pop());
	}

	while(m_unusedMatrixStack.IsEmpty() == FALSE)
	{
		delete m_unusedMatrixStack.Pop();
	}

	// Release parameter cache stack
	FlushParameterCacheStack();
	while(m_unusedParameterCacheStack.IsEmpty() == FALSE)
	{
		delete m_unusedParameterCacheStack.Pop();
	}

	// Release lighting tree
	if(m_lightTree)
		m_lightTree->Release();

	// Clear off the light entities used by the light tree
	for(INT32 i = 0; i < m_recycledLightEntities.Count(); ++i)
	{
		m_recycledLightEntities[i]->Release();
	}
	m_recycledLightEntities.Clear();

	// Clear off lighting info structures
	for(INT32 i = 0; i < m_activeLightingInfo.Count(); ++i)
	{
		delete m_activeLightingInfo[i];
	}
	m_activeLightingInfo.Clear();

	// Reset dynamic map structures
	ResetDynamicMaps();
}

IRuInstanceManager *CRuSceneRenderer::GetInstanceManager()
{
	return m_instanceManager;
}

BOOL CRuSceneRenderer::SetInstanceManager(IRuInstanceManager *instanceManager)
{
	m_instanceManager = instanceManager;

	return TRUE;
}

UINT32 CRuSceneRenderer::GetNumPrimitivesRendered()
{
	return m_numPrimitivesRendered;
}

UINT32 CRuSceneRenderer::GetNumBatchesIssued()
{
	return m_numBatchesIssued;
}

BOOL CRuSceneRenderer::RecordPerformanceProfile(IRuStream *outputStream)
{
	if(m_perfProfileStream)
	{
		return FALSE;
	}

	m_perfProfileStream = outputStream;

	return TRUE;
}

void CRuSceneRenderer::SetCamera(CRuCamera *camera)
{
	m_camera = camera;
}

void CRuSceneRenderer::SetLightingProp(CRuEntity *lightingProp)
{
	INT32 lightCount = 0;

	// Clear old lighting tree
	m_lightTree->Clear();

	// Parse the lighting prop and extract lights
	m_entityStack.Clear();
	m_entityStack.Push(lightingProp);

	// Enumerate lights
	while(!m_entityStack.IsEmpty())
	{
		CRuEntity *curEntity = m_entityStack.Pop();

		if(curEntity->GetType().IsSubClassOf(IRuEntity_Container::Type))
		{
			// Push all entities held by the frame onto the entity stack
			IRuEntity_Container *frame = (IRuEntity_Container *) curEntity;
			for(INT32 i = 0; i < frame->GetNumEntities(); ++i)
			{
				// Push entity onto entity stack
				m_entityStack.Push(frame->GetEntity(i));
			}
		}
		else if(curEntity->GetType().IsSubClassOf(CRuEntity_Light::Type))
		{
			CRuEntity_Light *light;

			// Try to recycle a light entity from our existing list
			if(lightCount < m_recycledLightEntities.Count())
			{
				light = m_recycledLightEntities[lightCount];
			}
			// Otherwise create a new light entity
			else
			{
				light = ruNEW CRuEntity_Light();
				m_recycledLightEntities.Add(light);
			}

			// Set light parameters
			light->SetLight(((CRuEntity_Light *) curEntity)->GetLight());

			// Append light to tree and increment light count
			m_lightTree->AddEntity(light);
			++lightCount;
		}
	}
}

void CRuSceneRenderer::SetSpecularColor(DWORD specularColor)
{
	m_specularColor = specularColor;
}

void CRuSceneRenderer::SetSpecularDirection(const CRuVector3 &specularDir)
{
	m_specularDirection = specularDir;
}

CRuParameterBlock *CRuSceneRenderer::GetShaderParameterBlock()
{
	return &m_paramBlock;
}

void CRuSceneRenderer::Scene_Begin()
{
	// Clear the currently selected render target
	m_curRTIndex = -1;

	// Reset object ID
	m_nextObjectID = 0;

	// Clear the list of render targets
	m_renderTargets.Clear();

	// Clear the render queue
	m_renderQueue.Clear();

	// Clear sorting criteria lists
	m_sortCriterias[0].Clear();
	m_sortCriterias[1].Clear();
	m_sortCriterias[2].Clear();
	m_sortCriterias[3].Clear();
	m_sortCriterias[4].Clear();
	m_sortCriterias[5].Clear();
	m_sortCriterias[6].Clear();
	m_sortCriterias[7].Clear();
	m_sortCriterias[8].Clear();

	// Reset primitive count
	m_numPrimitivesRendered = 0;
	m_numBatchesIssued = 0;

	ResetDynamicMaps();

	g_ruResourceManager->ResetRenderTargetPools();
}

void CRuSceneRenderer::Scene_SetRenderTarget(IRuObject *renderTarget, INT32 priority, DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	// Attempt to find the render target in the list of currently used RTs
	for(INT32 i = 0; i < m_renderTargets.Count(); ++i)
	{
		// If the RT is already in the list, simply set the selected RT index and return
		if(m_renderTargets[i].m_renderTarget == renderTarget)
		{
			m_curRTIndex = i;
			return;
		}
	}

	// Build the render target descriptor
	RenderTargetDescriptor rtDescriptor;
	rtDescriptor.m_renderTarget = renderTarget;
	rtDescriptor.m_depthStencilSurface = NULL;
	rtDescriptor.m_priority = priority;
	rtDescriptor.m_clearFlags = clearFlags;
	rtDescriptor.m_clearColor = color;
	rtDescriptor.m_clearZ = z;
	rtDescriptor.m_clearStencil = stencil;
	rtDescriptor.m_cleared = FALSE;
	rtDescriptor.m_enableClipPlane = FALSE;
	rtDescriptor.m_renderCompleteCallback = NULL;

	// Add descriptor to list, and set the selected RT index
	m_renderTargets.Add(rtDescriptor);
	m_curRTIndex = m_renderTargets.Count() - 1;
}

void CRuSceneRenderer::Scene_Submit(CRuEntity *sceneRoot, CRuCamera *camera)
{
	RUPROFILE_SAMPLE_BEGIN("scene manager");
	RUPROFILE_SAMPLE_BEGIN("submission");

	// Ensure the render target is non-null
	assert(m_curRTIndex >= 0);

	// If no camera is specified, use default camera
	if(camera == NULL)
	{
		if(m_camera == NULL)
		{
			return;
		}
		else
		{
			camera = m_camera;
		}
	}

	// If the entity is a valid decal target, put it into the list
	if(sceneRoot->GetOption(ruENTITYOPTION_DECALTARGET))
	{
		m_validDecalTargets.Add(sceneRoot);
	}

	// Clear entity stack and then push scene root onto entity stack
	m_entityStack.Clear();
	m_entityStack.Push(sceneRoot);

	// Enumerate objects for rendering
	while(!m_entityStack.IsEmpty())
	{
		CRuEntity *curEntity = m_entityStack.Pop();

		// If the entity is marked invisible, skip
		if(curEntity->GetOption(ruENTITYOPTION_INVISIBLE))
		{
			continue;
		}

		// Push all children onto entity stack
		CRuEntity *curChild = curEntity->GetFirstChild();
		while(curChild)
		{
			// Push child onto work stack
			m_entityStack.Push(curChild);

			// Advance to next child
			curChild = curChild->GetNextSibling();
		}

		// If the entity is a subclass of the type portal, set its view camera
		if(curEntity->GetType().IsSubClassOf(CRuContainer_Portal::Type))
		{
			static_cast<CRuContainer_Portal *>(curEntity)->SetCamera(camera);
		}

		// If the entity is renderable, process it
		if(curEntity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
		{
			IRuEntity_Renderable *renderableEntity = static_cast<IRuEntity_Renderable *>(curEntity);

			// Place all meshes(mesh + material + world transform) onto rendering queue
			RenderQueueEntry rqEntry;

			// Find the frame hierarchy to use for this entity ...
			CRuFrameHierarchy *frameHierarchy = NULL;
			CRuEntity *parentEntity = curEntity->GetParent();
			while(parentEntity != NULL)
			{
				// Use the parent entity as the frame hierarchy if it is one and if a frame hierarchy hasn't already been found
				if(frameHierarchy == NULL && parentEntity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
				{
					frameHierarchy = static_cast<CRuFrameHierarchy *>(parentEntity);
				}

				// If the parent entity has any shader parameter blocks, append them to the queue

				// Go up one level in the tree
				parentEntity = parentEntity->GetParent();
			}

			// Create a render queue entry for every mesh in the renderable entity
			for(INT32 i = 0, numMeshes = renderableEntity->GetNumMeshes(); i < numMeshes; ++i)
			{
				rqEntry.m_rtIndex = m_curRTIndex;
				rqEntry.m_camera = m_camera;
				rqEntry.m_frustumOverride = NULL;
				rqEntry.m_worldTransform = &renderableEntity->GetWorldTransform();
				rqEntry.m_paramCache = CreateParameterCache();
				rqEntry.m_entity = curEntity;
				rqEntry.m_hierarchy = frameHierarchy;
				rqEntry.m_noReplication = FALSE;
				rqEntry.m_objectID = m_nextObjectID++;

				// Get the i-th mesh and material
				if(renderableEntity->GetMesh(i, &rqEntry.m_mesh, &rqEntry.m_material) == FALSE)
					continue;

				// Store pointer to shader
				rqEntry.m_shader = rqEntry.m_material->GetShader();

				// Scan for dynamic textures
				Scene_ScanForDynamicTextures(rqEntry);

				// TODO: If the mesh is morphed, tween it

				// If the mesh is skinned and there is a hierarchy available, skin it
				if(frameHierarchy && rqEntry.m_entity->GetOption(ruENTITYOPTION_SKINNED))
				{
					CachedSkin cachedSkin;

					// TODO: Verify correctness of this 64 bit data type
					UINT64 meshKey = (reinterpret_cast<UINT64>(curEntity) << 32) | reinterpret_cast<UINT64>(rqEntry.m_mesh);
					DWORD meshKeyHash = RuGlobalHashGen().GetHashCode(meshKey);

					// Locate the cached skin mesh for the current entry
					if(m_skinningCache->Get(meshKey, meshKeyHash, cachedSkin) == FALSE)
					{
						// Create a new cached skin mesh for the current entry
						cachedSkin.m_mesh = ruNEW CRuSkinnableMesh();
						cachedSkin.m_mesh->SetSourceMesh(rqEntry.m_mesh);

						// Make space in the skinning cache
						CachedSkin reclaimedCachedSkin;
						while(m_skinningCache->Reclaim(meshKey, meshKeyHash, 1, reclaimedCachedSkin))
						{
							ruSAFE_RELEASE(reclaimedCachedSkin.m_mesh);
						}

						// Put cached skin entry into list
						m_skinningCache->Add(meshKey, meshKeyHash, cachedSkin, 1);
					}

					// Skin the mesh
//					cachedSkin.m_mesh->SetPose(frameHierarchy->GetSubNodeTransforms(), frameHierarchy->GetNumSubNodes());

					rqEntry.m_mesh = cachedSkin.m_mesh;
				}

				// blah?
				m_parameterBlocks.Clear();
				rqEntry.m_paramCache->ClearParameterCache();

				// Set material parameters into shader
				const CRuParameterBlock *matParamBlock = rqEntry.m_material->GetParameterBlock();
				if(matParamBlock)
				{
					m_parameterBlocks.Queue(matParamBlock);
				}

				// Find all parameter blocks and queue them into the parameter block queue
				CRuEntity *parentEntity = rqEntry.m_entity;
				while(parentEntity != NULL)
				{
					// NOTE: multiple parameter blocks for an entity must be added backwards!
					// otherwise the order of execution is not correct!

					// If the parent entity has any shader parameter blocks, append them to the queue
					m_parameterBlocks.Queue(parentEntity->GetParameterBlock());

					// Go up one level in the tree
					parentEntity = parentEntity->GetParent();
				}

				// Apply parameter blocks backwards to ensure correct priority
				while(m_parameterBlocks.IsEmpty() == FALSE)
				{
					// Cache all parameters in the parameter block
					rqEntry.m_paramCache->CacheParameterBlock(m_parameterBlocks.DequeueTail());
				}

				// Retrieve alpha value and store entity priority
				DWORD alphaParameter;
				REAL globalAlpha = 1.0f;

				if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_GLOBAL_ALPHA, alphaParameter))
				{
					globalAlpha = RuCastDWORDToFloat(alphaParameter);
				}

				if(globalAlpha < 1.0f)
				{
					const CRuMatrix4x4 &worldMat = rqEntry.m_entity->GetWorldTransform();
					CRuVector3 entityPos(worldMat._41, worldMat._42, worldMat._43);
					rqEntry.m_camera->GetFrustumTransform().TransformPointProjected(entityPos);

					rqEntry.m_priority = 1.0f - entityPos.z;
				}
				else
				{
					rqEntry.m_priority = 0.0f;
				}

				// Add entry to the render queue
				Scene_QueueRenderEntry(rqEntry);
			}
		}
	}

	RUPROFILE_SAMPLE_END("scene manager");
	RUPROFILE_SAMPLE_END("submission");
}

void CRuSceneRenderer::Scene_Submit(CRuMatrix4x4 *worldTransform, IRuMesh *mesh, IRuMaterial *material)
{
	RUPROFILE_SAMPLE_BEGIN("scene manager");
	RUPROFILE_SAMPLE_BEGIN("submission");

	// Ensure the render target is non-null
	assert(m_curRTIndex >= 0);

	RenderQueueEntry rqEntry;

	rqEntry.m_rtIndex = m_curRTIndex;
	rqEntry.m_camera = m_camera;
	rqEntry.m_frustumOverride = NULL;

	rqEntry.m_worldTransform = worldTransform;
	rqEntry.m_entity = NULL;
	rqEntry.m_mesh = mesh;
	rqEntry.m_material = material;
	rqEntry.m_shader = material->GetShader();
	rqEntry.m_noReplication = FALSE;

	rqEntry.m_objectID = m_nextObjectID++;

	// Add entry to the render queue
	Scene_QueueRenderEntry(rqEntry);

	RUPROFILE_SAMPLE_END("scene manager");
	RUPROFILE_SAMPLE_END("submission");
}

void CRuSceneRenderer::Scene_Flush()
{
	INT32 curRTIndex = -1;
	CRuCamera *curCamera = NULL;
	const CRuPlane4 *curFrustumPlanes = NULL;
	const CRuPlane4 *curBackdropFrustumPlanes = NULL;
	CRuVector3 viewSpaceSpecularDir;

	// Error check
	if(!m_instanceManager || !m_camera)
		return;

	RUPROFILE_SAMPLE_BEGIN("scene manager");

	// Store the number of render queue entries before processing for dynamic maps
	INT32 endIndex = m_renderQueue.Count() - 1;

	// Initialize the NULL texture
	if(m_nullTexture == NULL)
	{
		// If even this fails, nothing should have worked anyway
		m_nullTexture = g_ruResourceManager->CreateTexture("RUSYSTEM_NULL", 1, 1, ruSURFACEFMT_A8R8G8B8);
		assert(m_nullTexture);

		DWORD *pixels;
		INT32 pitch;
		
		if(m_nullTexture->Lock(0, (void **) &pixels, &pitch))
		{
			pixels[0] = 0xFFFFFFFF;

			m_nullTexture->Unlock(0);
		}
	}
/*
	// Initialize the cubic filter look-up texture
	if(m_cubicFilterLUT == NULL)
	{
		INT32 luTexSize = 128;

//		m_cubicFilterLUT = g_ruResourceManager->CreateTexture("lu_tex", luTexSize, 1, 1, ruSURFACEFMT_A8R8G8B8);
		m_cubicFilterLUT = g_ruResourceManager->CreateTexture("lu_tex", luTexSize, 1, 1, ruSURFACEFMT_A16B16G16R16);


		UINT16 *luTexData;
		INT32 luTexPitch;

		m_cubicFilterLUT->Lock(0, reinterpret_cast<void **>(&luTexData), &luTexPitch);

		float  h      = 1.0f / (luTexSize - 1);
		float  c      = 1.0f / 6.0f;
		float  phi[4], x3, x2, x = 0.0f;

		for(INT32 i = 0; i < luTexSize; ++i, x += h)
		{
			x3 = (x2 = x * x) * x;

			// cubic spline basis functions
			// non-negative partition of one; in [0, 2/3]
			phi[0] = c * (  -x3 + 3.0f *   x2 - 3.0f *    x + 1.0f);
			phi[1] = c * ( 3.0f *   x3 - 6.0f *   x2 + 4.0f);
			phi[2] = c * (-3.0f *   x3 + 3.0f *   x2 + 3.0f *    x + 1.0f);
			phi[3] = c * (   x3);

			// offsets; second with changed sign; in [0, 1]
			*luTexData++ = (1.0f + x - phi[1] / (phi[0] + phi[1])) * 65535.0f;
			*luTexData++ = (1.0f - x + phi[3] / (phi[2] + phi[3])) * 65535.0f;

			// weight for left sample (lerp: right weight = 1-left weight)
			*luTexData++ = (phi[0] + phi[1]) * 65535.0f;

			*luTexData++ = 65535.0f;
		}

		m_cubicFilterLUT->Unlock(0);
	}
*/
	// Process reflect/refract maps
	for(INT32 i = 0; i < m_dynamicMaps.Count(); ++i)
	{
		switch(m_dynamicMaps[i].m_mapChannel)
		{
			default:
				{
					for(INT32 j = 0; j <= endIndex; ++j)
					{
						RenderQueueEntry &curEntry = m_renderQueue[j];

						// If this entry is a no-replication entry, skip
						if(curEntry.m_noReplication || curEntry.m_entity->GetOption(ruENTITYOPTION_BACKDROP))
							continue;

						// Replicate
						RenderQueueEntry replicatedEntry = curEntry;
						replicatedEntry.m_rtIndex = m_dynamicMaps[i].m_rtIndex;
						replicatedEntry.m_camera = m_dynamicMaps[i].m_camera;

						// Add entry to the render queue
						Scene_QueueRenderEntry(replicatedEntry);
					}
				}

				break;

			case ruTEXCHANNEL_TERRAIN_PROJECTION:
				{
					IRuShader *sha = g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_FLAT");

					for(INT32 j = 0; j <= endIndex; ++j)
					{
						RenderQueueEntry &curEntry = m_renderQueue[j];

						if(curEntry.m_dynamicMapType == ruTEXCHANNEL_TERRAIN_PROJECTION)
						{
							// Replicate
							RenderQueueEntry replicatedEntry = curEntry;
							replicatedEntry.m_rtIndex = m_dynamicMaps[i].m_rtIndex;
							replicatedEntry.m_camera = m_dynamicMaps[i].m_camera;
							replicatedEntry.m_shader = sha;

							// Add entry to the render queue
							Scene_QueueRenderEntry(replicatedEntry);
						}
					}
				}

				break;
		}
	}


	// NOTE: Even closer now... some artifacts with rotating the view resulting in the perspective flipping,
	//       or vanishing to nothing. But otherwise decent resolution even with 512x512 maps.

	// Create the auxiliary depth-stencil surface
	if(m_auxDepthStencilSurface == NULL)
	{
		m_auxDepthStencilSurface = g_ruEngine->CreateDepthStencilSurface(1024, 1024, ruSURFACEFMT_D24S8);
	}

	// Shadow map test
	IRuTexture *shadowMapTexture = g_ruResourceManager->GetPooledRenderTarget(1024, ruSURFACEFMT_R32F);

	CRuVector3 shadowDir(0.0f, -1.0f, 0.0f);

	shadowDir.Set(-0.5f, -1.5f, 0.5f);
	shadowDir.Normalize();

	CRuMatrix4x4 shadowMatrix;
	CRuMatrix4x4 shadowMatrix2;

	if(g_ruRendererOptions.m_shadow_Enable)
	{
		INT32 smIndex = 0;
		if(shadowMapTexture)
		{
			if(m_altCamera == NULL)
			{
				m_altCamera = ruNEW CRuCamera();
			}

			if(m_shadowCamera == NULL)
			{
				m_shadowCamera = ruNEW CRuCamera();
			}

			// Copy camera settings and then reduce far plane
			*m_altCamera = *m_camera;
			m_altCamera->SetViewDistance(g_ruRendererOptions.m_shadow_Cutoff);
			m_altCamera->GetFrustumTransform();

			m_shadowCamera->SetTargetPosition(m_camera->GetCameraPosition());
			m_shadowCamera->SetCameraPosition(m_camera->GetCameraPosition() - shadowDir * 2500.0f);
			m_shadowCamera->SetOrthographicProjection(1.0f, 3000.0f, 2000.0f, 2000.0f);
			m_shadowCamera->SetTSMProjection(m_altCamera);

			shadowMatrix = m_shadowCamera->GetFrustumTransform();

			// reset camera
			m_shadowCamera->SetTargetPosition(m_camera->GetCameraPosition());
			m_shadowCamera->SetCameraPosition(m_camera->GetCameraPosition() - shadowDir * 2500.0f);
			m_shadowCamera->SetOrthographicProjection(1.0f, 3000.0f, 2000.0f, 2000.0f);
			shadowMatrix2 = m_shadowCamera->GetFrustumTransform();

			// Submit the texture to a list of textures that need to be rendered to
			DynamicMapDescriptor shadowMapDesc;
			shadowMapDesc.m_renderTarget = shadowMapTexture;
			shadowMapDesc.m_mapChannel = ruTEXCHANNEL_SHADOWDEPTHMAP;
			shadowMapDesc.m_camera = m_shadowCamera;
			shadowMapDesc.m_camera->AddRef();

			// Build the render target descriptor
			RenderTargetDescriptor shadowMapRTDescriptor;
			shadowMapRTDescriptor.m_renderTarget = shadowMapTexture;
			shadowMapRTDescriptor.m_depthStencilSurface = m_auxDepthStencilSurface;
			shadowMapRTDescriptor.m_priority = 0;
			shadowMapRTDescriptor.m_clearFlags = RUCLEAR_TARGET | RUCLEAR_ZBUFFER | RUCLEAR_STENCIL;
			shadowMapRTDescriptor.m_clearColor = 0xFFFFFFFF;
			shadowMapRTDescriptor.m_clearZ = 1.0f;
			shadowMapRTDescriptor.m_clearStencil = 0;
			shadowMapRTDescriptor.m_cleared = FALSE;
			shadowMapRTDescriptor.m_enableClipPlane = FALSE;
			shadowMapRTDescriptor.m_renderCompleteCallback = NULL;

			// Add descriptor to list, and set the selected RT index
			shadowMapDesc.m_rtIndex = m_renderTargets.Count();
			m_renderTargets.Add(shadowMapRTDescriptor);

			// Add dynamic map descriptor to a list
			m_dynamicMaps.Add(shadowMapDesc);
			smIndex = m_dynamicMaps.Count() - 1;
		}

		if(m_shadowMaterial == NULL)
		{
			m_shadowMaterial = ruNEW CRuMaterial();
			m_shadowMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_STATIC_SHADOW"));
		}

		if(m_shadowMapMaterial == NULL)
		{
			m_shadowMapMaterial = ruNEW CRuMaterial();
			m_shadowMapMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_DEPTHMAP"));
		}

		IRuShader *packedTerrainShader = g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED");
		IRuShader *packedTerrainShadowMapShader = g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_DEPTHMAP");
		IRuShader *packedTerrainShadowShader = g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_SHADOW");

		for(INT32 j = 0; j <= endIndex; ++j)
		{
			// If this entry is a no-replication entry, skip
			if(m_renderQueue[j].m_noReplication || m_renderQueue[j].m_entity->GetOption(ruENTITYOPTION_BACKDROP))
				continue;

			// Check shadow caster class
			if(m_renderQueue[j].m_entity->GetOption(ruENTITYOPTION_CASTERCLASS) < g_ruRendererOptions.m_shadow_CasterLevel)
				continue;

			// Replicate entry
			RenderQueueEntry replicatedEntry = m_renderQueue[j];
			replicatedEntry.m_rtIndex = m_dynamicMaps[smIndex].m_rtIndex;
			replicatedEntry.m_camera = m_dynamicMaps[smIndex].m_camera;

			if(replicatedEntry.m_material->GetShader() == packedTerrainShader)
			{
				replicatedEntry.m_shader = packedTerrainShadowMapShader;
			}
			else
			{
				replicatedEntry.m_shader = m_shadowMapMaterial->GetShader();
			}

			// Add entry to the render queue
			Scene_QueueRenderEntry(replicatedEntry);

			// Check shadow receiver class
			if(m_renderQueue[j].m_entity->GetOption(ruENTITYOPTION_RECEIVERCLASS) < g_ruRendererOptions.m_shadow_ReceiverLevel)
				continue;

			replicatedEntry = m_renderQueue[j];
			replicatedEntry.m_camera = m_camera;
			replicatedEntry.m_frustumOverride = m_altCamera->GetFrustumPlanes();

			if(replicatedEntry.m_material->GetShader() == packedTerrainShader)
			{
				replicatedEntry.m_shader = packedTerrainShadowShader;
			}
			else
			{
				replicatedEntry.m_shader = m_shadowMaterial->GetShader();
			}

			// Add entry to the render queue
			Scene_QueueRenderEntry(replicatedEntry);
		}
	}

	// Alternate terrain rendering mode

	RUPROFILE_SAMPLE_BEGIN("sorting");

	// Sort the rendering queue (Sorting is done on the first pass only)
	for(INT32 i = 8; i >= 0; --i)
	{
		m_radixSort.Sort((UINT32 *) m_sortCriterias[i].GetArray(), m_sortCriterias[i].Count(), FALSE, (i == 7) ? FALSE : TRUE);
	}

	RUPROFILE_SAMPLE_END("sorting");

	// Iterate through rendering queue and render each submodel
	UINT32 *sortedIndices = m_radixSort.GetSortedIndices();

	for(INT32 i = 0, numRQEntries = m_renderQueue.Count(); i < numRQEntries; ++i)
	{
		RenderQueueEntry &curEntry = m_renderQueue[sortedIndices[i]];

		if(curEntry.m_camera != curCamera)
		{
			curCamera = curEntry.m_camera;
			curFrustumPlanes = curCamera->GetFrustumPlanes();
			curBackdropFrustumPlanes = curCamera->GetBackdropFrustumPlanes();
		}

		BOOL backdropEntity = curEntry.m_entity->GetOption(ruENTITYOPTION_BACKDROP);
		BOOL ignoreVisibility = curEntry.m_entity->GetOption(ruENTITYOPTION_IGNOREVISIBILITY);

		// Check for visibility in frustum
		if(!ignoreVisibility)
		{
			if(curEntry.m_frustumOverride == NULL)
			{
				if(!backdropEntity)
				{
					if(IntersectsFrustum(curFrustumPlanes, curEntry.m_entity->GetWorldBounds()) == FALSE)
						continue;
				}
				else
				{
					if(IntersectsFrustum(curBackdropFrustumPlanes, curEntry.m_entity->GetWorldBounds()) == FALSE)
						continue;
				}
			}
			else
			{
				if(IntersectsFrustum(curEntry.m_frustumOverride, curEntry.m_entity->GetWorldBounds()) == FALSE)
					continue;
			}
		}

		// Perform skinning
		if(curEntry.m_mesh && curEntry.m_mesh->GetType() == CRuSkinnableMesh::Type)
		{
			// Skin the mesh
			static_cast<CRuSkinnableMesh *>(curEntry.m_mesh)->SetPose(curEntry.m_hierarchy->GetSubNodeTransforms(), curEntry.m_hierarchy->GetNumSubNodes());
		}
	}

	for(INT32 i = 0, numRQEntries = m_renderQueue.Count(); i < numRQEntries; ++i)
	{
		RenderQueueEntry &curEntry = m_renderQueue[sortedIndices[i]];

		// Setup render target if necessary
		if(curEntry.m_rtIndex != curRTIndex)
		{
			// TODO: this isn't really the right place to put this, semantically speaking
			// Trigger render complete callback
			if(curRTIndex >= 0 && m_renderTargets[curRTIndex].m_renderCompleteCallback)
			{
				m_renderTargets[curRTIndex].m_renderCompleteCallback(this, m_renderTargets[curRTIndex].m_renderTarget);
			}

			// Store index to new render target
			curRTIndex = curEntry.m_rtIndex;

			// Select the render target
			if(m_renderTargets[curRTIndex].m_renderTarget->GetType().IsSubClassOf(IRuSurface::Type))
			{
				static_cast<IRuSurface *>(m_renderTargets[curRTIndex].m_renderTarget)->SelectRenderTarget();
			}
			else if(m_renderTargets[curRTIndex].m_renderTarget->GetType().IsSubClassOf(IRuSwapChain::Type))
			{
				static_cast<IRuSwapChain *>(m_renderTargets[curRTIndex].m_renderTarget)->SelectRenderTarget();
			}
			else if(m_renderTargets[curRTIndex].m_renderTarget->GetType().IsSubClassOf(IRuTexture::Type))
			{
				// NOTE: When we're given a texture instead of a surface, level 0 is always selected as the render target
				static_cast<IRuTexture *>(m_renderTargets[curRTIndex].m_renderTarget)->SelectRenderTarget(0);
			}
			else
			{
				// Log an error
			}

			// Select the depth stencil surface
			if(m_renderTargets[curRTIndex].m_depthStencilSurface)
			{
				m_renderTargets[curRTIndex].m_depthStencilSurface->SelectDepthStencil();
			}
			else
			{
				g_ruEngine->RestoreDefaultDepthStencil();
			}

			// Clear the render target if necessary
			if(m_renderTargets[curRTIndex].m_cleared == FALSE)
			{
				m_renderTargets[curRTIndex].m_cleared = TRUE;
				g_ruEngine->Clear(m_renderTargets[curRTIndex].m_clearFlags, m_renderTargets[curRTIndex].m_clearColor, m_renderTargets[curRTIndex].m_clearZ, m_renderTargets[curRTIndex].m_clearStencil);
			}

			if(m_renderTargets[curRTIndex].m_enableClipPlane == TRUE)
			{
				// Set clip plane
				g_ruEngineD3D->GetD3DDevice()->SetClipPlane(0, m_renderTargets[curRTIndex].m_clipPlane.m_elements);

				// Enable clip plane
				g_ruEngineD3D->SetRenderState(D3DRS_CLIPPLANEENABLE, TRUE);
			}
			else
			{
				// Enable clip plane
				g_ruEngineD3D->SetRenderState(D3DRS_CLIPPLANEENABLE, FALSE);
			}
		}

		if(curEntry.m_camera != curCamera)
		{
			curCamera = curEntry.m_camera;
			curFrustumPlanes = curCamera->GetFrustumPlanes();
			curBackdropFrustumPlanes = curCamera->GetBackdropFrustumPlanes();

			if(curCamera->IsCameraReflected())
			{
				// Reverse culling mode
				g_ruEngineD3D->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
			}
			else
			{
				// Normal culling mode
				g_ruEngineD3D->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
			}

			curCamera->GetCameraTransform().TransformVector(m_specularDirection, viewSpaceSpecularDir);

			// TEST
//			CRuVector3 test = m_specularDirection * 50.0f;
//			viewSpaceSpecularDir = test;
		}

		BOOL backdropEntity = curEntry.m_entity->GetOption(ruENTITYOPTION_BACKDROP);
		BOOL ignoreVisibility = curEntry.m_entity->GetOption(ruENTITYOPTION_IGNOREVISIBILITY);

		// Check for visibility in frustum
		if(!ignoreVisibility)
		{
			if(curEntry.m_frustumOverride == NULL)
			{
				if(!backdropEntity)
				{
					if(IntersectsFrustum(curFrustumPlanes, curEntry.m_entity->GetWorldBounds()) == FALSE)
						continue;
				}
				else
				{
					if(IntersectsFrustum(curBackdropFrustumPlanes, curEntry.m_entity->GetWorldBounds()) == FALSE)
						continue;
				}
			}
			else
			{
				if(IntersectsFrustum(curEntry.m_frustumOverride, curEntry.m_entity->GetWorldBounds()) == FALSE)
					continue;
			}
		}

		// Decal handling -- broadcast valid decal target array to the entity
		curEntry.m_entity->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_CAMERA, reinterpret_cast<INT32>(curCamera), 0, NULL, 0.0f);
		curEntry.m_entity->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_DECALTARGET, reinterpret_cast<INT32>(&m_validDecalTargets), 0, NULL, 0.0f);

		// Ensure the mesh, material, and shader are valid
		if(curEntry.m_mesh && curEntry.m_material && curEntry.m_shader && curEntry.m_mesh->GetNumVertices() > 0 && curEntry.m_mesh->GetNumIndices() > 0)
		{
			IRuShader *shader = curEntry.m_shader;

			RUPROFILE_SAMPLE_BEGIN("parameters");

			// Clear the parameter cache
			m_paramCache0.ClearParameterCache();
			m_paramCache1.ClearParameterCache();

			// Check for billboarding here? And override all of the transforms?
			INT32 billboardType = curEntry.m_entity->GetOption(ruENTITYOPTION_BILLBOARD);
			switch(billboardType)
			{
				// Normal entity, no billboarding
				case ruBILLBOARDTYPE_NONE:
					{
						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) curEntry.m_worldTransform);
						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &curEntry.m_camera->GetCameraTransform());
					}

					break;

				// Unconstrained billboard
				case ruBILLBOARDTYPE_UNCONSTRAINED:
					{
						CRuMatrix4x4 combinedWorldViewTransform = (*curEntry.m_worldTransform) * curEntry.m_camera->GetCameraTransform();

						CRuMatrix4x4 *identityMatrix = CreateMatrix();
						identityMatrix->SetToIdentity();

						CRuMatrix4x4 *aggregateMatrix = CreateMatrix();
						aggregateMatrix->SetToBillboard(combinedWorldViewTransform, CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));
						*aggregateMatrix = (*aggregateMatrix) * combinedWorldViewTransform;

						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) identityMatrix);
						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) aggregateMatrix);
					}

					break;

				case ruBILLBOARDTYPE_AXIAL:
					{
						CRuMatrix4x4 combinedWorldViewTransform = (*curEntry.m_worldTransform) * curEntry.m_camera->GetCameraTransform();

						CRuMatrix4x4 *identityMatrix = CreateMatrix();
						identityMatrix->SetToIdentity();

						CRuMatrix4x4 *aggregateMatrix = CreateMatrix();
						aggregateMatrix->SetToAxialBillboard(combinedWorldViewTransform, CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));
						*aggregateMatrix = (*aggregateMatrix) * combinedWorldViewTransform;

						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) identityMatrix);
						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) aggregateMatrix);
					}

					break;

				case ruBILLBOARDTYPE_AXIAL_CUSTOM:
					{
						CRuMatrix4x4 combinedWorldViewTransform = (*curEntry.m_worldTransform) * curEntry.m_camera->GetCameraTransform();

						CRuMatrix4x4 *identityMatrix = CreateMatrix();
						identityMatrix->SetToIdentity();

						CRuMatrix4x4 *aggregateMatrix = CreateMatrix();
						aggregateMatrix->SetToAxialBillboard(combinedWorldViewTransform, CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));
						*aggregateMatrix = (*aggregateMatrix) * combinedWorldViewTransform;

						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) identityMatrix);
						m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) aggregateMatrix);
					}

					break;
			}

			if(!backdropEntity)
			{
				m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &curEntry.m_camera->GetProjectionTransform());
			}
			else
			{
				m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &curEntry.m_camera->GetBackdropProjectionTransform());
			}

			m_paramCache0.CacheParameter(ruPARAMTYPE_SHADOW_DIR, (DWORD) &shadowDir);
			m_paramCache0.CacheParameter(ruPARAMTYPE_SHADOW_FADE_START, RuCastFloatToDWORD(g_ruRendererOptions.m_shadow_Fade_Start));
			m_paramCache0.CacheParameter(ruPARAMTYPE_SHADOW_FADE_END, RuCastFloatToDWORD(g_ruRendererOptions.m_shadow_Fade_End));
			m_paramCache0.CacheParameter(ruPARAMTYPE_SHADOW_STRENGTH, RuCastFloatToDWORD(g_ruRendererOptions.m_shadow_Strength));
			m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_SHADOW, (DWORD) &shadowMatrix);
			m_paramCache0.CacheParameter(ruPARAMTYPE_TRANSFORM_SHADOW2, (DWORD) &shadowMatrix2);
			m_paramCache0.CacheParameter(ruPARAMTYPE_SHADOW_OBJECTID, (DWORD) curEntry.m_objectID);

			// Set lighting information into the shader (TODO: Only do this if required by shader)
			LightingInfo *lightInfo = BuildLightingInfo(static_cast<IRuEntity_Renderable *>(curEntry.m_entity));
			m_paramCache0.CacheParameter(ruPARAMTYPE_LIGHT_AMBIENT, (DWORD) &lightInfo->m_ambientLight);
			m_paramCache0.CacheParameter(ruPARAMTYPE_LIGHT_OBJ_SH9, (DWORD) lightInfo->m_shCoefficients);

			// Set default alpha
			m_paramCache0.CacheParameter(ruPARAMTYPE_GLOBAL_ALPHAMODE, ruALPHAMODE_NORMAL);
			m_paramCache0.CacheParameter(ruPARAMTYPE_GLOBAL_ALPHA, RuCastFloatToDWORD(1.0f));

			// Set specular lighting parameters
//			m_paramCache0.CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_POS, (DWORD) &viewSpaceSpecularDir);
			m_paramCache0.CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_POS, (DWORD) &lightInfo->m_specularPosition);
			m_paramCache0.CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_COLOR, (DWORD) m_specularColor);

			// Set scene parameters into shader
			m_paramCache0.CacheParameterBlock(&m_paramBlock);

			RUPROFILE_SAMPLE_END("parameters");

			// Set default parameters into shader
			shader->SetParameters(&m_defaultParamCache);

			// Set outer loop parameters into shader
			shader->SetParameters(&m_paramCache0);

			// Set entity paramters into shader
			shader->SetParameters(curEntry.m_paramCache);

			// Store tweening morph targets
			DWORD param;
			INT32 morphTarget0 = 0, morphTarget1 = -1;
			if(curEntry.m_paramCache->GetParameter(ruPARAMTYPE_TWEEN_MORPHTARGET_0, param))
			{
				morphTarget0 = (INT32) param;
			}

			if(curEntry.m_paramCache->GetParameter(ruPARAMTYPE_TWEEN_MORPHTARGET_1, param))
			{
				morphTarget1 = (INT32) param;
			}

			// Iterate through all passes of the shader
			for(INT32 curPass = 0, numPasses = shader->GetNumPasses(); curPass < numPasses; ++curPass)
			{
				RUPROFILE_SAMPLE_BEGIN("texture");

				// Setup texture bindings
				for(INT32 curSampler = 0, numSamplers = shader->GetNumSamplers(curPass); curSampler < numSamplers; ++curSampler)
				{
					IRuBaseTexture *texture = curEntry.m_material->GetTexture(shader->GetTextureChannel(curPass, curSampler));

					if(texture)
					{
						texture->Select(curSampler);
					}
					else
					{
						if(shader->GetTextureChannel(curPass, curSampler) == ruTEXCHANNEL_SHADOWDEPTHMAP && shadowMapTexture)
						{
							shadowMapTexture->Select(curSampler);
						}
						else
						{
							// Force NULL texture
							m_nullTexture->Select(curSampler);
						}
					}
				}

				RUPROFILE_SAMPLE_END("texture");

				RUPROFILE_SAMPLE_BEGIN("instance");

				// Instance the mesh
				IRuInstancedMesh *instancedMesh = m_instanceManager->GetInstance(curEntry.m_mesh, shader);

				RUPROFILE_SAMPLE_END("instance");

				// Abort if the mesh could not be instanced
				if(instancedMesh == NULL)
					continue;

				// Render each batch
				for(INT32 curBatch = 0; ; ++curBatch)
				{
					INT32 batchOffset, batchNumPrimitives;

					// Get batch settings
					if(curEntry.m_mesh->GetBatch(curBatch, batchOffset, batchNumPrimitives) == FALSE)
						break;

					RUPROFILE_SAMPLE_BEGIN("parameters");

					// Set batch parameters into shader
					const CRuParameterBlock *paramBlock = curEntry.m_mesh->GetBatchParameterBlock(curBatch);
					if(paramBlock)
					{
						m_paramCache1.CacheParameterBlock(paramBlock);
					}
					shader->SetParameters(&m_paramCache1);

					RUPROFILE_SAMPLE_END("parameters");

					RUPROFILE_SAMPLE_BEGIN("render");

//					LARGE_INTEGER start, stop, freq;
//					QueryPerformanceCounter(&start);

					// Render
					shader->BeginPass(curPass);
					m_numPrimitivesRendered += instancedMesh->Render(batchOffset, batchNumPrimitives, morphTarget0, morphTarget1);
					++m_numBatchesIssued;
					shader->EndPass(curPass);
/*
					QueryPerformanceCounter(&stop);
					QueryPerformanceFrequency(&freq);

					if(m_perfProfileStream)
					{
						INT32 ticks = (stop.QuadPart - start.QuadPart);
						INT32 ticksPerMS = freq.QuadPart / 1000;
						REAL period = (REAL) ticks / ticksPerMS;

						char woot[200];
						sprintf(woot, "%f ms\r\n", period);

						m_perfProfileStream->Write(woot, strlen(woot));
					}
*/
					RUPROFILE_SAMPLE_END("render");
				}
			}
		}

//		}
	}

	if(m_perfProfileStream)
	{
		if(m_query == NULL)
		{
			g_ruEngineD3D->GetD3DDevice()->CreateQuery(D3DQUERYTYPE_EVENT, &m_query);
			m_query->Issue(D3DISSUE_END);
		}

		LARGE_INTEGER start, stop, freq;
		QueryPerformanceCounter(&start);

		while(S_FALSE == m_query->GetData( NULL, 0, D3DGETDATA_FLUSH));

		QueryPerformanceCounter(&stop);

		QueryPerformanceFrequency(&freq);

		INT32 ticks = (stop.QuadPart - start.QuadPart);
		INT32 ticksPerMS = freq.QuadPart / 1000;
		REAL period = (REAL) ticks / ticksPerMS;


		char woot[200];
		sprintf(woot, "%f ms\n\r", period);

		m_perfProfileStream->Write(woot, strlen(woot));
	}

	if(m_query)
	{
		m_query->Issue(D3DISSUE_END);
		while(S_FALSE == m_query->GetData( NULL, 0, D3DGETDATA_FLUSH));
	}

	// Clear valid decal targets list
	m_validDecalTargets.Clear();

	// Clear the render queue
	m_renderQueue.Clear();

	// Clear sorting criteria lists
	m_sortCriterias[0].Clear();
	m_sortCriterias[1].Clear();
	m_sortCriterias[2].Clear();
	m_sortCriterias[3].Clear();
	m_sortCriterias[4].Clear();
	m_sortCriterias[5].Clear();
	m_sortCriterias[6].Clear();
	m_sortCriterias[7].Clear();
	m_sortCriterias[8].Clear();

	while(m_usedMatrixStack.IsEmpty() == FALSE)
	{
		m_unusedMatrixStack.Push(m_usedMatrixStack.Pop());
	}

	FlushParameterCacheStack();

	RUPROFILE_SAMPLE_END("scene manager");
}

void CRuSceneRenderer::Scene_End()
{
	Scene_Flush();

	if(m_perfProfileStream)
	{
		delete m_perfProfileStream;
		m_perfProfileStream = NULL;
	}
}

void CRuSceneRenderer::Shadow_RenderCompleteCallback(IRuSceneRenderer *sceneRenderer, IRuObject *renderTarget)
{
}

/*!
	Begins 2D immediate-mode rendering. This function must be invoked before using any of the 2D toolkit
	functionality. When finished with the 2D toolkit, IRuSceneRenderer::Ru2D_End must be invoked to flush the
	2D pipeline.

	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_Begin()
{
	// Get pointers to each component
	m_ru2D_Mesh_Position = static_cast<CRuMorphTarget *>(m_ru2D_Mesh->GetMorphTarget(0))->GetPosition();
	m_ru2D_Mesh_Diffuse = static_cast<CRuMorphTarget *>(m_ru2D_Mesh->GetMorphTarget(0))->GetDiffuse();
	m_ru2D_Mesh_UV0 = static_cast<CRuMorphTarget *>(m_ru2D_Mesh->GetMorphTarget(0))->GetTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);
	m_ru2D_Mesh_Indices = m_ru2D_Mesh->GetIndices();

	// Set reference render target dimensions
	g_ruEngine->GetRenderTargetSize(m_ru2D_RefRTWidth, m_ru2D_RefRTHeight);

	// Reset effect type
	m_ru2D_EffectType = ru2DFXTYPE_NULL;
	
	// Reset rotate mode
	m_ru2D_RotateMode = ru2DROTATE_NONE;

	// Reset submission mode
	m_ru2D_LineMode = FALSE;

	return TRUE;
}

/*!
	Ends 2D immediate-mode rendering. Must be invoked inside a IRuSceneRenderer::Scene_Begin and
	IRuSceneRenderer::Scene_End pair. This function will flush any remaining geometry in the
	renderer buffer.

	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_End()
{
	// Flush current batch
	Ru2D_RenderBatch();

	return TRUE;
}

BOOL CRuSceneRenderer::Ru2D_SetViewportScale(REAL width, REAL height)
{
	m_ru2D_Viewport_Width = width;
	m_ru2D_Viewport_Height = height;

	return TRUE;
}

IRuTexture *CRuSceneRenderer::Ru2D_GetReservedDXT5Texture()
{
	return m_ru2D_DXT5AtlasTexture;
}

BOOL CRuSceneRenderer::Ru2D_SetScissorRect(CRuRectangle *scissorRect)
{
/*
	RECT rect;

	rect.left = ((scissorRect->m_left / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
	rect.right = ((scissorRect->m_right / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
	rect.top = -(((scissorRect->m_top / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);
	rect.bottom = -(((scissorRect->m_bottom / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);
*/

	// Force flush of the current batch
	Ru2D_RenderBatch();

	if(scissorRect)
	{
		RECT rect;

		rect.left = (INT32) scissorRect->m_left;
		rect.right = (INT32) scissorRect->m_right;
		rect.top = (INT32) scissorRect->m_top;
		rect.bottom = (INT32) scissorRect->m_bottom;

		return g_ruEngine->SetScissorRect(&rect);
	}

	return g_ruEngine->SetScissorRect(NULL);
}

/*!
	Sets the current effect type for the 2D toolkit. Calling this function will cause the
	renderer buffer to be flushed.

	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_SetEffectType(Ru2D_EffectType effectType)
{
	// Flush current batch if necessary
	if(m_ru2D_EffectType != effectType)
	{
		Ru2D_RenderBatch();
	}

	// Store effect type
	m_ru2D_EffectType = effectType;

	// Select the active shader based on effect type
	IRuShader *shader = NULL;
	switch(effectType)
	{
		case ru2DFXTYPE_FLAT:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_FLAT");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_ADD:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_ADD");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_FILTER:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_FILTER");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_FILTER_CUBIC:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_FILTER_CUBIC");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_MASK:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_MASK");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_MASKED:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_MASKED");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_BLIT_LUMINANCE:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_BLIT_LUMINANCE");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_DARKEN:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_DARKEN");
				m_ru2D_DiffuseColor = 0x7777777F;
			}

			break;
		case ru2DFXTYPE_GAUSSIAN:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_GAUSSIAN");
				m_ru2D_DiffuseColor = 0x7777777F;
			}

			break;
		case ru2DFXTYPE_MODULATE:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_MODULATE");
				m_ru2D_DiffuseColor = 0xFFFFFFFF;
			}

			break;
		case ru2DFXTYPE_SPLAT_3:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_SPLAT_PS11");
				m_ru2D_DiffuseColor = 0x7777777F;
			}

			break;
		case ru2DFXTYPE_SPLAT_6:
			{
				shader = g_ruResourceManager->GetShader("ru2DFXTYPE_SPLAT_PS20");
				m_ru2D_DiffuseColor = 0x7777777F;
			}

			break;
	}

	// If the shader was properly selected, return TRUE
	if(shader)
	{
		m_ru2D_Material->SetShader(shader);

		return TRUE;
	}

	return FALSE;
}

/*!
	Sets an effect parameter.

	@param effectParam The effect parameter to set.
	@param parameter The value to set
	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_SetEffectParameter(Ru2D_EffectParameter effectParam, DWORD parameter)
{
	switch(effectParam)
	{
		case ru2DFXPARAM_STRENGTH:	// Parameter 0% to 100%
			{
				UINT8 strength = (UINT8) ((parameter / 100.0f) * 255.0f);
				m_ru2D_DiffuseColor = RUCOLOR_ARGB(strength, strength, strength, strength);
			}

			break;
		case ru2DFXPARAM_COLOR:
			{
				m_ru2D_DiffuseColor = parameter;
			}

			break;
		case ru2DFXPARAM_ROTATE:
			{
				m_ru2D_RotateMode = (Ru2D_RotateMode) parameter;
			}

			break;
	}

	return TRUE;
}

/*!
	Bypass the 2D toolkit interface and set a parameter directly into the 2D effect shader.
	Under normal circumstances, this function should never be invoked, as the function will
	fail if the shader does not support the requested parameter.

	@param paramType The shader parameter to set.
	@param parameter The value to set
	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_SetShaderParameter(RuParameterType paramType, DWORD param)
{
	IRuShader *shader = m_ru2D_Material->GetShader();

	if(shader)
	{
		m_paramCache0.CacheParameter(paramType, param);

		shader->SetParameters(&m_paramCache0);

		return TRUE;
	}

	return FALSE;
}

/*!
	Sets the current texture used by the 2D toolkit. Calling this function will
	cause the renderer buffer to be flushed if the specified texture changes
	the texture at the stage.

	@param texture The texture to set into the texture channel.
	@param texChannel The texture channel whose texture will be changed.
	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_SetTexture(IRuTexture *texture, RuTextureChannel texChannel)
{
	IRuTexture *boundTexture = NULL;

	if(texture)
	{
		// Store active texture handle
		m_ru2D_ActiveTextureHandle = m_ru2D_TextureAtlas->BindIntoAtlas(texture);
		boundTexture = m_ru2D_TextureAtlas->GetTexture(m_ru2D_ActiveTextureHandle);
	}

	// Flush current batch if necessary
	if(m_ru2D_Material->GetTexture(texChannel) != boundTexture)
	{
		Ru2D_RenderBatch();
	}

	// Set texture into texture channel
	m_ru2D_Material->SetTexture(texChannel, boundTexture);

	if(texture)
	{
		// Set reference texture dimensions
		switch(texChannel)
		{
			// Normally, use the [0, texture size] as [0, 1] in our UV coordinates
			case ruTEXCHANNEL_DIFFUSEMAP:
			case ruTEXCHANNEL_TERRAIN_ALPHA_0:
			case ruTEXCHANNEL_TERRAIN_ALPHA_1:
				{
					m_ru2D_RefTextureWidth = texture->GetLevelWidth(0);
					m_ru2D_RefTextureHeight = texture->GetLevelHeight(0);
				}

				break;

			// In the case of terrain layers, do not alter the reference texture size
			case ruTEXCHANNEL_TERRAIN_LAYER_0:
			case ruTEXCHANNEL_TERRAIN_LAYER_1:
			case ruTEXCHANNEL_TERRAIN_LAYER_2:
			case ruTEXCHANNEL_TERRAIN_LAYER_3:
			case ruTEXCHANNEL_TERRAIN_LAYER_4:
			case ruTEXCHANNEL_TERRAIN_LAYER_5:
				break;
		}
	}
	else
	{
		// If the texture is NULL, then just pick sensible texture width and height values to use in UV calculations
		if(m_ru2D_RefTextureWidth == 0 || m_ru2D_RefTextureHeight == 0)
		{
			m_ru2D_RefTextureWidth = 4;
			m_ru2D_RefTextureHeight = 4;
		}
	}

	return TRUE;
}

/*!
	Submits a single rectangle to the 2D toolkit for rendering.

	@param srcRect Source area of the texture to render from, in screen coordinates.
	@param destRect Destination area of the render target to render to, in screen coordinates.
*/
void CRuSceneRenderer::Ru2D_Submit(const CRuRectangle &srcRect, const CRuRectangle &destRect)
{
	Ru2D_Submit(1, &srcRect, &destRect, NULL);
}

void CRuSceneRenderer::Ru2D_ConvertCoordinates(CRuRectangle &rectangle)
{
	if(m_ru2D_Viewport_Width > 0.0f && m_ru2D_Viewport_Height > 0.0f)
	{
		REAL xFactor = m_ru2D_RefRTWidth / m_ru2D_Viewport_Width;
		REAL yFactor = m_ru2D_RefRTHeight / m_ru2D_Viewport_Height;

		rectangle.m_left = floor(rectangle.m_left * xFactor);
		rectangle.m_right = floor(rectangle.m_right * xFactor);
		rectangle.m_top = floor(rectangle.m_top * yFactor);
		rectangle.m_bottom = floor(rectangle.m_bottom * yFactor);
	}
}

/*!
	Submits a list of rectangles to the 2D toolkit for rendering.

	@param numRects Number of rectangles to submit.
	@param srcRect Source area of the texture to render from, in screen coordinates.
	@param destRect Destination area of the render target to render to, in screen coordinates.
	@param color List of RUCOLOR values to use for each rectangular region. If NULL, then the color set by ru2DFXPARAM_COLOR is used.
*/
void CRuSceneRenderer::Ru2D_Submit(INT32 numRects, const CRuRectangle *srcRect, const CRuRectangle *destRect, const DWORD *color)
{
	// Iterate through all rectangles
	for(INT32 i = 0; i < numRects; ++i)
	{
		// Flush current batch if necessary
		if(m_ru2D_PrimitiveCount >= m_ru2D_MaxPrimitiveCount)
		{
			Ru2D_RenderBatch();
		}

		// Perform coordinate conversion
		CRuRectangle src = srcRect[i];
		CRuRectangle dst = destRect[i];

//		Ru2D_ConvertCoordinates(src);
		Ru2D_ConvertCoordinates(dst);

		// Calculate destination vertices
		REAL pixelOffsetX = 0.5f / m_ru2D_RefRTWidth;
		REAL pixelOffsetY = 0.5f / m_ru2D_RefRTHeight;

		REAL x0 = ((dst.m_left / m_ru2D_RefRTWidth) - 0.5f) * 2.0f - pixelOffsetX;
		REAL x1 = ((dst.m_right / m_ru2D_RefRTWidth) - 0.5f) * 2.0f - pixelOffsetX;
		REAL y0 = -(((dst.m_top / m_ru2D_RefRTHeight) - 0.5f) * 2.0f - pixelOffsetY);
		REAL y1 = -(((dst.m_bottom / m_ru2D_RefRTHeight) - 0.5f) * 2.0f - pixelOffsetY);

		// Calculate source UV
		REAL u0 = src.m_left / (REAL) m_ru2D_RefTextureWidth;
		REAL u1 = src.m_right / (REAL) m_ru2D_RefTextureWidth;
		REAL v0 = src.m_top / (REAL) m_ru2D_RefTextureHeight;
		REAL v1 = src.m_bottom / (REAL) m_ru2D_RefTextureHeight;

		if(m_ru2D_ActiveTextureHandle >= 0)
		{
			UINT32 texWidth = m_ru2D_TextureAtlas->GetTextureWidth(m_ru2D_ActiveTextureHandle);
			UINT32 texHeight = m_ru2D_TextureAtlas->GetTextureHeight(m_ru2D_ActiveTextureHandle);

			// Choose UV offset based on whether we do linear filtering or not
			switch(m_ru2D_EffectType)
			{
				default:
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_left / (REAL) texWidth, src.m_top / (REAL) texHeight, 0.0f, 0.0f, u0, v0);
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_right / (REAL) texWidth, src.m_bottom / (REAL) texHeight, 0.0f, 0.0f, u1, v1);
					break;

				case ru2DFXTYPE_BLIT_FILTER:
				case ru2DFXTYPE_BLIT_MASK:
				case ru2DFXTYPE_BLIT_MASKED:
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_left / (REAL) texWidth, src.m_top / (REAL) texHeight, 1.0f, 1.0f, u0, v0);
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_right / (REAL) texWidth, src.m_bottom / (REAL) texHeight, -1.0f, -1.0f, u1, v1);
					break;
			}
		}
		else
		{
			// Choose UV offset based on whether we do linear filtering or not
			switch(m_ru2D_EffectType)
			{
				default:
					u0 = src.m_left / (REAL) m_ru2D_RefTextureWidth;
					u1 = src.m_right / (REAL) m_ru2D_RefTextureWidth;
					v0 = src.m_top / (REAL) m_ru2D_RefTextureHeight;
					v1 = src.m_bottom / (REAL) m_ru2D_RefTextureHeight;
					break;

				case ru2DFXTYPE_BLIT_FILTER:
					u0 = src.m_left / (REAL) m_ru2D_RefTextureWidth;
					u1 = src.m_right / (REAL) m_ru2D_RefTextureWidth;
					v0 = src.m_top / (REAL) m_ru2D_RefTextureHeight;
					v1 = src.m_bottom / (REAL) m_ru2D_RefTextureHeight;
					break;
			}

		}

		// Setup vertices
		INT32 vertexCount = m_ru2D_PrimitiveCount * 3;
		INT32 uvIndex = vertexCount * 2;
		DWORD vertexColor = m_ru2D_DiffuseColor;

		if(color)
		{
			vertexColor = color[i];
		}

		m_ru2D_Mesh_Position[vertexCount].Set(x0, y0, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = vertexColor;
		++vertexCount;

		m_ru2D_Mesh_Position[vertexCount].Set(x1, y0, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = vertexColor;
		++vertexCount;

		m_ru2D_Mesh_Position[vertexCount].Set(x0, y1, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = vertexColor;
		++vertexCount;

		m_ru2D_Mesh_Position[vertexCount].Set(x1, y1, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = vertexColor;
		++vertexCount;

		switch(m_ru2D_RotateMode)
		{
			// No rotation
			case ru2DROTATE_NONE:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;
				}

				break;

			// 90 degree clock-wise rotation
			case ru2DROTATE_CLOCKWISE_90:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;
				}

				break;

			// 180 degree clock-wise rotation
			case ru2DROTATE_CLOCKWISE_180:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;
				}

				break;

			// 270 degree clock-wise rotation
			case ru2DROTATE_CLOCKWISE_270:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;
				}

				break;

			case ru2DROTATE_FLIP_U:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;
				}

				break;

			case ru2DROTATE_FLIP_V:
				{
					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v1;

					m_ru2D_Mesh_UV0[uvIndex++] = u0;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;

					m_ru2D_Mesh_UV0[uvIndex++] = u1;
					m_ru2D_Mesh_UV0[uvIndex++] = v0;
				}

				break;

		}


		// Setup indices
		vertexCount -= 4;
		UINT16 *indicesOut = &m_ru2D_Mesh_Indices[vertexCount];
		*indicesOut++ = vertexCount;
		*indicesOut++ = vertexCount + 1;
		*indicesOut++ = vertexCount + 2;
		*indicesOut++ = vertexCount + 1;
		*indicesOut++ = vertexCount + 3;
		*indicesOut++ = vertexCount + 2;

		// Update primitive information
		m_ru2D_PrimitiveCount += 2;
	}

	// Update mesh information
	m_ru2D_Mesh->SetNumPrimitives(m_ru2D_PrimitiveCount);
	m_ru2D_Mesh->SetNumVertices(m_ru2D_PrimitiveCount * 3);
}

/*!
	Begins fan submission mode. This mode allows the user to indicate a mapping from the source
	texture to the destination render target, as specified by srcRect and destRect. This mapping
	is used to calculate UV coordinates, and if any vertices lie outside the mapping region, the
	UV coordinates will be incorrect. After entering this mode, the first vertex submitted must be
	the center of the triangle fan. The remaining vertex submissions are the edge vertices of the
	fan, radiating from the center. At least three vertices must be submitted for any output to be
	displayed.

	@param srcRect Source area of the texture to render from, in screen coordinates.
	@param destRect Destination area of the render target to render to, in screen coordinates.
*/
void CRuSceneRenderer::Ru2D_Fan_Begin(const CRuRectangle &srcRect, const CRuRectangle &destRect)
{
	// Clear any vertices
	m_ru2D_VertexList.Clear();

	// Save the source and destination regions
	m_ru2D_Fan_SrcRegion = srcRect;
	m_ru2D_Fan_DestRegion = destRect;
}

/*!
	Ends fan submission mode.
*/
void CRuSceneRenderer::Ru2D_Fan_End()
{
	if(m_ru2D_VertexList.Count() >= 3)
	{
		REAL u0 = m_ru2D_Fan_SrcRegion.m_left / (REAL) m_ru2D_RefTextureWidth;
		REAL u1 = m_ru2D_Fan_SrcRegion.m_right / (REAL) m_ru2D_RefTextureWidth;
		REAL v0 = m_ru2D_Fan_SrcRegion.m_top / (REAL) m_ru2D_RefTextureHeight;
		REAL v1 = m_ru2D_Fan_SrcRegion.m_bottom / (REAL) m_ru2D_RefTextureHeight;

		// Convert into a triangle list
		INT32 curIndices[3] = { 0, 0, 0 };
		INT32 numTriangles = m_ru2D_VertexList.Count() - 2;
		INT32 vertexCount = m_ru2D_PrimitiveCount * 3;
		INT32 uvIndex = vertexCount * 2;
		UINT16 *indicesOut = &m_ru2D_Mesh_Indices[vertexCount];
		for(INT32 i = 0; i < numTriangles; ++i)
		{
			curIndices[1] = i + 1;
			curIndices[2] = i + 2;

			for(INT32 j = 0; j < 3; ++j)
			{
				// Convert screen coordinates into canonical coordinates
				REAL x = ((m_ru2D_VertexList[curIndices[j]].x / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
				REAL y = -(((m_ru2D_VertexList[curIndices[j]].y / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);

				m_ru2D_Mesh_Position[vertexCount].Set(x, y, 0.5f);
				m_ru2D_Mesh_Diffuse[vertexCount] = m_ru2D_DiffuseColor;

				// Calculate vertex UV
				REAL tu = (m_ru2D_VertexList[curIndices[j]].x - m_ru2D_Fan_DestRegion.m_left) / (m_ru2D_Fan_DestRegion.m_right - m_ru2D_Fan_DestRegion.m_left);
				REAL tv = (m_ru2D_VertexList[curIndices[j]].y - m_ru2D_Fan_DestRegion.m_top) / (m_ru2D_Fan_DestRegion.m_bottom - m_ru2D_Fan_DestRegion.m_top);
				tu = min(1.0f, max(0.0f, tu));
				tv = min(1.0f, max(0.0f, tv));

				m_ru2D_Mesh_UV0[uvIndex++] = u0 + tu * (u1 - u0);
				m_ru2D_Mesh_UV0[uvIndex++] = v0 + tv * (v1 - v0);

				*indicesOut++ = vertexCount;

				++vertexCount;
			}
		}

		// Update primitive information
		m_ru2D_PrimitiveCount += numTriangles;

		// Update mesh information
		m_ru2D_Mesh->SetNumPrimitives(m_ru2D_PrimitiveCount);
		m_ru2D_Mesh->SetNumVertices(m_ru2D_PrimitiveCount * 3);
	}
}

/*!
	Submits a triangle fan vertex.

	@param fanVertex Triangle fan vertex
*/
void CRuSceneRenderer::Ru2D_Fan_Submit(const CRuVector3 &fanVertex)
{
	m_ru2D_VertexList.Add(fanVertex);
}

/*!
	Begins line submission mode.
*/
void CRuSceneRenderer::Ru2D_Line_Begin()
{
	// Flush current batch
	Ru2D_RenderBatch();

	// Enable line mode
	m_ru2D_LineMode = TRUE;

	// Clear any vertices
	m_ru2D_VertexList.Clear();
}

/*!
	Ends line submission mode.
*/
void CRuSceneRenderer::Ru2D_Line_End()
{
	m_ru2D_Mesh->SetPrimitiveType(ruPRIMTYPE_LINELIST);

	// Flush current batch
	Ru2D_RenderBatch();

	m_ru2D_Mesh->SetPrimitiveType(ruPRIMTYPE_TRIANGLELIST);

	m_ru2D_LineMode = FALSE;
}

/*!
	Submits a line vertex.

	@param lineVertex The next vertex on the line.
*/
void CRuSceneRenderer::Ru2D_Line_Submit(const CRuVector3 &lineVertex)
{
	if(m_ru2D_VertexList.Count() > 0)
	{
		INT32 vertexCount = m_ru2D_PrimitiveCount * 2;
		UINT16 *indicesOut = &m_ru2D_Mesh_Indices[vertexCount];

		// Convert screen coordinates into canonical coordinates
		REAL x1 = ((lineVertex.x / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
		REAL y1 = -(((lineVertex.y / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);

		// Store vertex
		m_ru2D_Mesh_Position[vertexCount].Set(m_ru2D_Mesh_Position[vertexCount - 1]);
		m_ru2D_Mesh_Diffuse[vertexCount] = m_ru2D_Mesh_Diffuse[vertexCount - 1];
		*indicesOut++ = vertexCount;

		++vertexCount;
		m_ru2D_Mesh_Position[vertexCount].Set(x1, y1, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = m_ru2D_DiffuseColor;
		*indicesOut++ = vertexCount;

		// Update primitive information
		++m_ru2D_PrimitiveCount;

		// Update mesh information
		m_ru2D_Mesh->SetNumPrimitives(m_ru2D_PrimitiveCount);
		m_ru2D_Mesh->SetNumVertices(m_ru2D_PrimitiveCount * 2);
	}
}

/*!
	Submits a line segment

	@param lineVertex0 Vertex 0 of the segment.
	@param lineVertex1 Vertex 1 of the segment.
*/
void CRuSceneRenderer::Ru2D_Line_Submit(const CRuVector3 &lineVertex0, const CRuVector3 &lineVertex1)
{
	CRuVector3 vertices[2] = { lineVertex0, lineVertex1 };

	Ru2D_Line_Submit(1, vertices, NULL);
}

/*!
	Submits a set of line segments with color

	@param numLines Number of line segments to submit.
	@param lineVertices Vertices of the line segments. Must have numLines * 2 vertices.
	@param color List of RUCOLOR values to use for each line segment. If NULL, then the color set by ru2DFXPARAM_COLOR is used.
*/
void CRuSceneRenderer::Ru2D_Line_Submit(INT32 numLines, const CRuVector3 *lineVertices, const DWORD *color)
{
	INT32 vertexCount = m_ru2D_PrimitiveCount * 2;
	UINT16 *indicesOut = &m_ru2D_Mesh_Indices[vertexCount];

	for(INT32 i = 0, j = 0, k = 1; i < numLines; ++i, ++j, ++k)
	{
		// Convert screen coordinates into canonical coordinates
		REAL x0 = ((lineVertices[j].x / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
		REAL y0 = -(((lineVertices[j].y / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);
		REAL x1 = ((lineVertices[k].x / m_ru2D_RefRTWidth) - 0.5f) * 2.0f;
		REAL y1 = -(((lineVertices[k].y / m_ru2D_RefRTHeight) - 0.5f) * 2.0f);

		DWORD diffuseColor0 = m_ru2D_DiffuseColor;
		DWORD diffuseColor1 = m_ru2D_DiffuseColor;
		if(color)
		{
			diffuseColor0 = color[j];
			diffuseColor1 = color[k];
		}

		// Store vertex
		m_ru2D_Mesh_Position[vertexCount].Set(x0, y0, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = diffuseColor0;
		*indicesOut++ = vertexCount;

		++vertexCount;
		m_ru2D_Mesh_Position[vertexCount].Set(x1, y1, 0.5f);
		m_ru2D_Mesh_Diffuse[vertexCount] = diffuseColor1;
		*indicesOut++ = vertexCount;
	}

	// Update primitive information
	m_ru2D_PrimitiveCount += numLines;

	// Update mesh information
	m_ru2D_Mesh->SetNumPrimitives(m_ru2D_PrimitiveCount);
	m_ru2D_Mesh->SetNumVertices(m_ru2D_PrimitiveCount * 2);
}

BOOL CRuSceneRenderer::Ru3D_Begin()
{
	// Disable line mode
	m_ru3D_LineMode = FALSE;

	return TRUE;
}

BOOL CRuSceneRenderer::Ru3D_End()
{
	// Flush current batch
	Ru2D_RenderBatch();

	return TRUE;
}

BOOL CRuSceneRenderer::Ru3D_SetShader(IRuShader *shader)
{
	m_ru3D_Material->SetShader(shader);

	return TRUE;
}

BOOL CRuSceneRenderer::Ru3D_SetShaderParameter(RuParameterType paramType, DWORD param)
{
	IRuShader *shader = m_ru3D_Material->GetShader();

	if(shader)
	{
		m_paramCache0.CacheParameter(paramType, param);

		shader->SetParameters(&m_paramCache0);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Ru3D_SetTexture(IRuTexture *texture, RuTextureChannel texChannel)
{
	// Flush current batch
	Ru3D_RenderBatch();

	// Set texture
	m_ru3D_Material->SetTexture(texChannel, texture);

	return TRUE;
}

void CRuSceneRenderer::Ru3D_Line_Begin()
{
	// Flush current batch
	Ru3D_RenderBatch();

	// Enable line mode
	m_ru3D_LineMode = TRUE;

	// Clear any vertices
	m_ru3D_VertexList.Clear();
}

void CRuSceneRenderer::Ru3D_Line_End()
{
	DWORD diffuseColor = 0xFFFFFFFF;

	m_ru3D_Mesh->SetPrimitiveType(ruPRIMTYPE_LINELIST);

	// Get pointers to each component
	CRuVector3 *position = static_cast<CRuMorphTarget *>(m_ru3D_Mesh->GetMorphTarget(0))->GetPosition();
	DWORD *diffuse = static_cast<CRuMorphTarget *>(m_ru3D_Mesh->GetMorphTarget(0))->GetDiffuse();
	UINT16 *indices = m_ru3D_Mesh->GetIndices();

	INT32 vertexCount = m_ru3D_PrimitiveCount * 2;
	UINT16 *indicesOut = &indices[vertexCount];
	for(INT32 i = 1; i < m_ru3D_VertexList.Count(); ++i)
	{
		// Store vertex
		position[vertexCount].Set(m_ru3D_VertexList[i - 1]);
		diffuse[vertexCount] = diffuseColor;

		// Store index
		*indicesOut++ = vertexCount;
		++vertexCount;

		// Store vertex
		position[vertexCount].Set(m_ru3D_VertexList[i]);
		diffuse[vertexCount] = diffuseColor;

		// Store index
		*indicesOut++ = vertexCount;
		++vertexCount;

		// Update primitive information
		++m_ru3D_PrimitiveCount;
	}

	// Update mesh information
	m_ru3D_Mesh->SetNumPrimitives(m_ru3D_PrimitiveCount);
	m_ru3D_Mesh->SetNumVertices(m_ru3D_PrimitiveCount * 2);

	// Flush current batch
	Ru3D_RenderBatch();

	m_ru3D_Mesh->SetPrimitiveType(ruPRIMTYPE_TRIANGLELIST);

	m_ru3D_LineMode = FALSE;
}

void CRuSceneRenderer::Ru3D_Line_Submit(const CRuVector3 &lineVertex)
{
	m_ru3D_VertexList.Add(lineVertex);
}

void CRuSceneRenderer::Ru3D_Line_Submit(const CRuAABB &boundingBox)
{
	// Face at z = minima
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Minima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Minima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Maxima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Minima().y, boundingBox.Minima().z));

	// Face at y = minima
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Minima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Minima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Minima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Minima().y, boundingBox.Minima().z));

	// Face at y = maxima
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Maxima().y, boundingBox.Minima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Maxima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Minima().z));

	// Face at z = maxima
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Maxima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Maxima().x, boundingBox.Minima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Minima().y, boundingBox.Maxima().z));
	m_ru3D_VertexList.Add(CRuVector3(boundingBox.Minima().x, boundingBox.Maxima().y, boundingBox.Maxima().z));
}

CRuMatrix4x4 *CRuSceneRenderer::CreateMatrix()
{
	CRuMatrix4x4 *newMatrix = NULL;

	// Create matrix
	if(m_unusedMatrixStack.IsEmpty())
	{
		newMatrix = ruNEW CRuMatrix4x4();
	}
	else
	{
		newMatrix = m_unusedMatrixStack.Pop();
	}

	// Push matrix onto used matrix stack
	m_usedMatrixStack.Push(newMatrix);

	return newMatrix;
}

CRuParameterCache *CRuSceneRenderer::CreateParameterCache()
{
	CRuParameterCache *paramCache = NULL;

	// Get a parameter cache object
	if(m_unusedParameterCacheStack.IsEmpty())
	{
		paramCache = ruNEW CRuParameterCache();
	}
	else
	{
		paramCache = m_unusedParameterCacheStack.Pop();
	}

	// Push parameter cache object onto used stack
	m_usedParameterCacheStack.Push(paramCache);

	return paramCache;
}

void CRuSceneRenderer::FlushParameterCacheStack()
{
	while(m_usedParameterCacheStack.IsEmpty() == FALSE)
	{
		m_unusedParameterCacheStack.Push(m_usedParameterCacheStack.Pop());
	}
}

void CRuSceneRenderer::FlushSkinningCache()
{
	m_skinningCache->ForAllElements(FlushSkinningCacheCallback, this);
	m_skinningCache->Clear();
}

BOOL CRuSceneRenderer::FlushSkinningCacheCallback(const void *key, const void *data, void *userData)
{
	CachedSkin cachedSkin = *((CachedSkin *) data);

	ruSAFE_RELEASE(cachedSkin.m_mesh);

	return TRUE;
}

void CRuSceneRenderer::ResetDynamicMaps()
{
	for(INT32 i = 0; i < m_dynamicMaps.Count(); ++i)
	{
		m_dynamicMaps[i].m_camera->Release();
	}
	m_dynamicMaps.Clear();
}

void CRuSceneRenderer::AddtoSH9(CRuColor4 *shCoefficients, CRuColor4 &lightColor, CRuVector3 &dirN)
{
	shCoefficients[0] += lightColor * m_sh9_Const1;
	shCoefficients[1] += lightColor * (m_sh9_Const2 * dirN.x);
	shCoefficients[2] += lightColor * (m_sh9_Const2 * dirN.y);
	shCoefficients[3] += lightColor * (m_sh9_Const2 * dirN.z);
	shCoefficients[4] += lightColor * m_sh9_Const3 * (dirN.x * dirN.z);
	shCoefficients[5] += lightColor * m_sh9_Const3 * (dirN.z * dirN.y);
	shCoefficients[6] += lightColor * m_sh9_Const3 * (dirN.y * dirN.x);
	shCoefficients[7] += lightColor * m_sh9_Const4 * (3.0f * dirN.z * dirN.z - 1.0f);
	shCoefficients[8] += lightColor * m_sh9_Const5 * (dirN.x * dirN.x - dirN.y * dirN.y);
}

CRuSceneRenderer::LightingInfo *CRuSceneRenderer::BuildLightingInfo(IRuEntity_Renderable *renderableEntity)
{
	// Retrieve the renderable entity's tag
	LightingInfo *lightInfo = static_cast<LightingInfo *>(renderableEntity->GetTag(ruENTITYTAG_SYSTEM));

	if(lightInfo == NULL)
	{
		// Create a new lighting info structure and set it as the renderable entity's tag
		lightInfo = ruNEW LightingInfo();
		lightInfo->m_entityIteration = 0;
		lightInfo->m_lightingIteration = m_iterationNumber - 1;
		renderableEntity->SetTag(ruENTITYTAG_SYSTEM, lightInfo);

		// Add lighting info to the active lighting info list
		m_activeLightingInfo.Add(lightInfo);
	}

	// If the lighting info is not at the current iteration, rebuild it
//	if(lightInfo->m_entityIteration != renderableEntity->GetIteration() || lightInfo->m_lightingIteration != m_iterationNumber)
	{
		lightInfo->m_ambientLight.Set(1.0f, 0.1f, 0.1f, 0.1f);
		
		for(INT32 i = 0; i < 9; ++i)
		{
			lightInfo->m_shCoefficients[i].Set(0.0f, 0.0f, 0.0f, 0.0f);
		}

		// Update iteration number
//		lightInfo->m_entityIteration = renderableEntity->GetIteration();
//		lightInfo->m_lightingIteration = m_iterationNumber;
//		lightInfo->m_lightingIteration = m_iterationNumber - 1;

		// Get the entity's inverse world transform, used for transforming light into object space
		// NOTE: Except for spot lights, it is enough to simply invert the translation. For now, we hack around this. Will need to be changed later.
//		CRuMatrix4x4 invWT = renderableEntity->GetWorldTransform().GetInverse();
		CRuMatrix4x4 invWT = renderableEntity->GetWorldTransform();
		invWT.Orthonormalize();
		invWT = invWT.GetInverse();

		// Get the bounds of the entity in world space
		CRuSphere entityBounds(renderableEntity->GetWorldBounds());
		renderableEntity->GetWorldTransform().TransformPoint(entityBounds.Center());

		// Transform specular position from world space into object space
		CRuVector3 localSpecularDirection;
		invWT.TransformVector(m_specularDirection, localSpecularDirection);
		lightInfo->m_specularPosition = localSpecularDirection * entityBounds.Radius();

		// Query lighting tree for nearby lights
		m_enumeratedLights.Clear();
		m_enumeratedLightDistances.Clear();
		m_lightTree->GetEntities(entityBounds, m_enumeratedLights);

		// Sort enumerated lights
		for(INT32 i = 0; i < m_enumeratedLights.Count(); ++i)
		{
			const CRuLight &light = static_cast<CRuEntity_Light *>(m_enumeratedLights[i])->GetLight();

			switch(light.m_type)
			{
				default:
					{
						m_enumeratedLightDistances.Add(0.0f);
					}

					break;
				case ruLIGHTTYPE_POINT:
					{
						m_enumeratedLightDistances.Add((entityBounds.Center() - light.m_position).Magnitude());
					}

					break;
			}
		}

		m_radixSort2.Sort(m_enumeratedLightDistances.GetArray(), m_enumeratedLightDistances.Count());

		CRuVector3 objSpaceDirection;

		// Sort lights into directional, ambient, point, and spot bins
		UINT32 *sortedIndices = m_radixSort2.GetSortedIndices();
		for(INT32 i = 0; i < m_enumeratedLights.Count(); ++i)
		{
			const CRuLight &light = static_cast<CRuEntity_Light *>(m_enumeratedLights[sortedIndices[i]])->GetLight();

			switch(light.m_type)
			{
				case ruLIGHTTYPE_DIRECTIONAL:
					{
						// Transform light direction into object space
						invWT.TransformVector(light.m_direction, objSpaceDirection);

						// Represent directional lights in the SH term
						REAL d = m_enumeratedLightDistances[sortedIndices[i]];
						REAL attenuation = 1.0f - min(1.0f, d / light.m_range);
						AddtoSH9(lightInfo->m_shCoefficients, light.m_diffuse * 1.0f, objSpaceDirection * -1.0f);
					}

					break;
				case ruLIGHTTYPE_POINT:
					{
/*
						if(lightInfo->m_numPointLights < 2)
						{
							lightInfo->m_pointLights[lightInfo->m_numPointLights] = light;

							invWT.TransformPoint(lightInfo->m_pointLights[lightInfo->m_numPointLights].m_position);

							++lightInfo->m_numPointLights;
						}
*/
						// Set one specular light

						// Approximate point lights in the SH term
						REAL d = m_enumeratedLightDistances[sortedIndices[i]];
						REAL attenuation = 1.0f - min(1.0f, d / light.m_range);
						AddtoSH9(lightInfo->m_shCoefficients, light.m_diffuse * attenuation, ((entityBounds.Center() - light.m_position) / d) * -1.0f);
					}

					break;
			}
		}
	}

	return lightInfo;
}

void CRuSceneRenderer::Scene_QueueRenderEntry(RenderQueueEntry &rqEntry)
{
	IRuShader *shader = rqEntry.m_shader;

	// Add entry to the render queue
	m_renderQueue.Add(rqEntry);

	// Use render target priority as the first sorting criteria
	m_sortCriterias[0].Add(m_renderTargets[rqEntry.m_rtIndex].m_priority);

	// Use render target address as the second sorting criteria
	m_sortCriterias[1].Add(reinterpret_cast<UINT32>(m_renderTargets[rqEntry.m_rtIndex].m_renderTarget));

	// Use shader priority as the third sorting criteria
	m_sortCriterias[2].Add(shader->GetShaderPriority());

	// Use shader address as the fourth sorting criteria
	m_sortCriterias[3].Add(reinterpret_cast<UINT32>(shader));

	// Use material priority as the fifth sorting criteria
	m_sortCriterias[4].Add(RuCastFloatToDWORD(rqEntry.m_material->GetMaterialPriority()));

	// Use entity priority as the sixth sorting criteria
	m_sortCriterias[5].Add(RuCastFloatToDWORD(rqEntry.m_priority));

	// Fill the rest of the sorting criteria using textures
	for(INT32 curSampler = 0; curSampler < 2; ++curSampler)
	{
		RuTextureChannel samplerChannel = shader->GetTextureChannel(0, curSampler);
		IRuBaseTexture *samplerTexture = NULL;

		if(samplerChannel != ruTEXCHANNEL_INVALID)
		{
			samplerTexture = rqEntry.m_material->GetTexture(samplerChannel);
		}

		m_sortCriterias[curSampler + 6].Add(reinterpret_cast<UINT32>(samplerTexture));
	}
}

void CRuSceneRenderer::Scene_ScanForDynamicTextures(RenderQueueEntry &rqEntry)
{
	// Get the material's shader
	IRuShader *shader = rqEntry.m_material->GetShader();

	// Iterate through all samplers
	for(INT32 curSampler = 0; curSampler < 8; ++curSampler)
	{
		RuTextureChannel samplerChannel = shader->GetTextureChannel(0, curSampler);

		switch(samplerChannel)
		{
			case ruTEXCHANNEL_TERRAIN_PROJECTION:
				{
					IRuTexture *renderTarget = NULL;

					// Search existing dynamic maps for one that applies to terrain projection
					for(INT32 i = 0; i < m_dynamicMaps.Count(); ++i)
					{
						// If there is an equivalent map, use it
						if(m_dynamicMaps[i].m_mapChannel == samplerChannel)
						{
							renderTarget = m_dynamicMaps[i].m_renderTarget;
							break;
						}
					}

					if(renderTarget == NULL)
					{
						// TODO: render target size should be adjustable
						renderTarget = g_ruResourceManager->GetPooledRenderTarget(512, ruSURFACEFMT_A8R8G8B8);

						if(renderTarget)
						{
							// Submit the texture to a list of textures that need to be rendered to
							DynamicMapDescriptor dynamicMapDesc;
							dynamicMapDesc.m_renderTarget = renderTarget;
							dynamicMapDesc.m_mapChannel = samplerChannel;
							dynamicMapDesc.m_camera = m_camera;
							dynamicMapDesc.m_camera->AddRef();

							// Build the render target descriptor
							RenderTargetDescriptor rtDescriptor;
							rtDescriptor.m_renderTarget = renderTarget;
							rtDescriptor.m_depthStencilSurface = NULL;
							rtDescriptor.m_priority = 0;
							rtDescriptor.m_clearFlags = RUCLEAR_TARGET | RUCLEAR_ZBUFFER | RUCLEAR_STENCIL;
							rtDescriptor.m_clearColor = 0x00000000;
							rtDescriptor.m_clearZ = 1.0f;
							rtDescriptor.m_clearStencil = 0;
							rtDescriptor.m_cleared = FALSE;
							rtDescriptor.m_enableClipPlane = FALSE;
							rtDescriptor.m_renderCompleteCallback = NULL;

							// Add descriptor to list, and set the selected RT index
							dynamicMapDesc.m_rtIndex = m_renderTargets.Count();
							m_renderTargets.Add(rtDescriptor);

							// Add dynamic map descriptor to a list
							m_dynamicMaps.Add(dynamicMapDesc);
						}
					}

					rqEntry.m_material->SetTexture(samplerChannel, renderTarget);
					rqEntry.m_dynamicMapType = samplerChannel;
				}

				break;

			case ruTEXCHANNEL_REFLECTIONMAP:
			case ruTEXCHANNEL_REFRACTIONMAP:
				{
					DWORD splitPlane;

					// get clip plane info from somewhere... the material possibly
					CRuPlane4 clipPlane(CRuVector3(0.0f, 1.0f, 0.0f), CRuVector3(0.0f, 0.0f, 0.0f));

					if(rqEntry.m_material->GetParameterBlock()->GetParameter(ruPARAMTYPE_WATER_SPLITPLANE, splitPlane))
					{
						clipPlane = *reinterpret_cast<CRuPlane4 *>(reinterpret_cast<void *>(splitPlane));
					}

					if(samplerChannel == ruTEXCHANNEL_REFRACTIONMAP)
					{
						clipPlane.m_a *= -1.0f;
						clipPlane.m_b *= -1.0f;
						clipPlane.m_c *= -1.0f;
						clipPlane.m_d *= -1.0f;
					}

					// Search existing dynamic maps for one that has equivalent split plane (reflection/refraction plane)
					BOOL equivalentMapFound = FALSE;
					for(INT32 i = 0; i < m_dynamicMaps.Count(); ++i)
					{
						// If there is an equivalent map, use it
						REAL dotNormal = m_dynamicMaps[i].m_splitPlane.DotNormal(clipPlane);
						if(dotNormal >= 0.9f && fabs(m_dynamicMaps[i].m_splitPlane.m_d - clipPlane.m_d) < 1.0f)
						{
							rqEntry.m_material->SetTexture(samplerChannel, m_dynamicMaps[i].m_renderTarget);

							// Disable replication for this entry
							rqEntry.m_noReplication = TRUE;

							equivalentMapFound = TRUE;

							break;
						}
					}

					if(equivalentMapFound)
						break;

					// TODO: render target size should be adjustable
					IRuTexture *renderTarget = g_ruResourceManager->GetPooledRenderTarget(512, ruSURFACEFMT_A8R8G8B8);

					if(renderTarget)
					{
						rqEntry.m_material->SetTexture(samplerChannel, renderTarget);

						// Submit the texture to a list of textures that need to be rendered to
						DynamicMapDescriptor dynamicMapDesc;
						dynamicMapDesc.m_renderTarget = renderTarget;
						dynamicMapDesc.m_mapChannel = samplerChannel;

						if(samplerChannel == ruTEXCHANNEL_REFLECTIONMAP)
						{
							dynamicMapDesc.m_camera = ruNEW CRuCamera();
							*dynamicMapDesc.m_camera = *m_camera;

							// Reflect current camera
							dynamicMapDesc.m_camera->Reflect(TRUE, clipPlane);
						}
						else
						{
							dynamicMapDesc.m_camera = m_camera;
							dynamicMapDesc.m_camera->AddRef();
						}

						// Transform clipping plane into clip space
						dynamicMapDesc.m_splitPlane = clipPlane;

						// Build the render target descriptor
						RenderTargetDescriptor rtDescriptor;
						rtDescriptor.m_renderTarget = renderTarget;
						rtDescriptor.m_depthStencilSurface = NULL;
						rtDescriptor.m_priority = 0;
						rtDescriptor.m_clearFlags = RUCLEAR_TARGET | RUCLEAR_ZBUFFER | RUCLEAR_STENCIL;
						rtDescriptor.m_clearColor = 0x00000000;
						rtDescriptor.m_clearZ = 1.0f;
						rtDescriptor.m_clearStencil = 0;
						rtDescriptor.m_cleared = FALSE;
						rtDescriptor.m_enableClipPlane = TRUE;
						rtDescriptor.m_clipPlane = dynamicMapDesc.m_splitPlane * dynamicMapDesc.m_camera->GetFrustumTransform();
						rtDescriptor.m_renderCompleteCallback = NULL;

						// Add descriptor to list, and set the selected RT index
						dynamicMapDesc.m_rtIndex = m_renderTargets.Count();
						m_renderTargets.Add(rtDescriptor);

						// Add dynamic map descriptor to a list
						m_dynamicMaps.Add(dynamicMapDesc);

						// Disable replication for this entry
						rqEntry.m_noReplication = TRUE;
					}
					else
					{
						rqEntry.m_material->SetTexture(samplerChannel, NULL);
					}
				}

				break;
		}
	}
}

void CRuSceneRenderer::Ru2D_RenderBatch()
{
	if(m_ru2D_PrimitiveCount > 0)
	{
		// Render call
		IRuShader *shader = m_ru2D_Material->GetShader();
		if(shader)
		{
			INT32 numPasses = shader->GetNumPasses();

			for(INT32 curPass = 0; curPass < numPasses; ++curPass)
			{
				// Instance the mesh
				IRuInstancedMesh *iMesh = g_ruInstanceManager->GetInstance(m_ru2D_Mesh, shader);

				// Continue if the mesh was instanced successfully
				if(iMesh)
				{
					// Setup texture bindings
					INT32 numSamplers = shader->GetNumSamplers(curPass);

					for(INT32 curSampler = 0; curSampler < numSamplers; ++curSampler)
					{
						IRuBaseTexture *texture = m_ru2D_Material->GetTexture(shader->GetTextureChannel(curPass, curSampler));

						if(texture)
						{
							texture->Select(curSampler);

							// Cache texture width and height parameters
							if(texture->GetType().IsSubClassOf(IRuTexture::Type))
							{
								m_paramCache0.CacheParameter(ruPARAMTYPE_2DFX_TEXTURE_WIDTH, static_cast<IRuTexture *>(texture)->GetLevelWidth(0));
								m_paramCache0.CacheParameter(ruPARAMTYPE_2DFX_TEXTURE_HEIGHT, static_cast<IRuTexture *>(texture)->GetLevelHeight(0));
							}
						}
						else
						{
/*							// temp hack
							if(shader->GetTextureChannel(curPass, curSampler) == ruTEXCHANNEL_DETAILMAP)
							{
								m_cubicFilterLUT->Select(curSampler);
							}
							else*/
							{
								// Force NULL texture
								g_ruEngine->ClearSampler(curSampler);
							}
						}
					}

					// Pass parameters to shader
					shader->SetParameters(&m_paramCache0);

					// Render
					shader->BeginPass(curPass);
					m_numPrimitivesRendered += iMesh->Render();
					++m_numBatchesIssued;
					shader->EndPass(curPass);
				}
			}
		}

		// Reset primitive count
		m_ru2D_PrimitiveCount = 0;
	}
}

void CRuSceneRenderer::Ru3D_RenderBatch()
{
	if(m_ru3D_PrimitiveCount > 0)
	{
		// Render call
		IRuShader *shader = m_ru3D_Material->GetShader();
		if(shader)
		{
			INT32 numPasses = shader->GetNumPasses();

			for(INT32 curPass = 0; curPass < numPasses; ++curPass)
			{
				// Instance the mesh
				IRuInstancedMesh *iMesh = g_ruInstanceManager->GetInstance(m_ru3D_Mesh, shader);

				// Continue if the mesh was instanced successfully
				if(iMesh)
				{
					// Setup texture bindings
					INT32 numSamplers = shader->GetNumSamplers(curPass);

					for(INT32 curSampler = 0; curSampler < numSamplers; ++curSampler)
					{
						IRuBaseTexture *texture = m_ru3D_Material->GetTexture(shader->GetTextureChannel(curPass, curSampler));

						if(texture)
						{
							texture->Select(curSampler);
						}
						else
						{
							// Force NULL texture
							g_ruEngine->ClearSampler(curSampler);
						}
					}

					// Pass parameters to shader

					// Render
					shader->BeginPass(curPass);
					m_numPrimitivesRendered += iMesh->Render();
					++m_numBatchesIssued;
					shader->EndPass(curPass);
				}
			}
		}

		// Reset primitive count
		m_ru3D_PrimitiveCount = 0;
	}
}

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

CRuRendererOptions				g_ruRendererOptions;
IRuSceneRenderer*				g_ruSceneRenderer = NULL;

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

#pragma managed
