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

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

//#include "../../Direct3D/Engine/RuEngineD3D.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_rendererState(ruRENDERERSTATE_STOPPED),
	m_enableRendering(TRUE),
	m_debug_NullRendering(FALSE),
	m_eventListeners(NULL),
	m_ru2D_CurrentRenderTarget(NULL),
	m_ru2D_CurrentViewport(NULL),
	m_iterationNumber(0),
	m_camera(NULL),
	m_nullTexture(NULL),
	m_clampTexture(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_UVMode(ru2DUVMODE_OFFSET),
	m_ru2D_EffectType(ru2DFXTYPE_FLAT),
	m_ru2D_MaxPrimitiveCount(84),
	m_ru2D_PrimitiveCount(0),
	m_ru2D_RefMaterial(NULL),
	m_ru2D_Mesh(NULL),
	m_ru2D_Material(NULL),
	m_ru2D_MaterialConstants(NULL),
	m_commandQueue(256),
	m_renderQueue(8192),
	m_lightTree(NULL),
	m_numPrimitivesRendered(0),
	m_numBatchesIssued(0),
	m_perfProfileStream(NULL),
	m_query(NULL)
{
	// Initialize identity transform
	m_identityTransform.SetToIdentity();

	// Create reference material
	m_ru2D_RefMaterial = ruNEW CRuMaterial();

	// Initialize variables required by 2D toolkit
	INT32 ru2DVertexCount = m_ru2D_MaxPrimitiveCount * 4;
	INT32 ru2DPrimitiveCount = m_ru2D_MaxPrimitiveCount * 2;

	m_ru2D_Mesh = ruNEW CRuMesh();
	m_ru2D_Mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, ru2DVertexCount, ru2DPrimitiveCount, ruVERTEXFORMAT_DIFFUSE);

	// Construct particle mesh geometry
	DWORD *diffuse = static_cast<CRuMorphTarget *>(m_ru2D_Mesh->GetMorphTarget(0))->GetDiffuse();
	UINT16 *indices = m_ru2D_Mesh->GetIndices();

	for(INT32 i = 0, vIdx = 0, fIdx = 0; i < m_ru2D_MaxPrimitiveCount; ++i)
	{
		diffuse[vIdx++] = (i << 24) | (0x00 << 16) | (0x00 << 8);
		diffuse[vIdx++] = (i << 24) | (0x00 << 16) | (0xFF << 8);
		diffuse[vIdx++] = (i << 24) | (0xFF << 16) | (0xFF << 8);
		diffuse[vIdx++] = (i << 24) | (0xFF << 16) | (0x00 << 8);

		INT32 vertexOffset = i * 4;

		indices[fIdx++] = vertexOffset + 0;
		indices[fIdx++] = vertexOffset + 2;
		indices[fIdx++] = vertexOffset + 1;

		indices[fIdx++] = vertexOffset + 0;
		indices[fIdx++] = vertexOffset + 3;
		indices[fIdx++] = vertexOffset + 2;
	}

	m_ru2D_Mesh->IncrementMeshIteration();
	m_ru2D_Mesh->IncrementTopologyIteration();

	// Setup a proxy mesh for each possible primitive count starting with zero
	m_ru2D_Mesh->ClearBatches();
	for(INT32 i = 0; i <= m_ru2D_MaxPrimitiveCount; ++i)
	{
		// Create new batch
		m_ru2D_Mesh->AddBatch(0, i * 2);

		// Create proxy mesh for the batch
		CRuProxyMesh *proxyMesh = ruNEW CRuProxyMesh();
		proxyMesh->SetProxiedMesh(m_ru2D_Mesh, i);

		m_ru2D_ProxyMeshes.Add(proxyMesh);
	}

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

	// Create skinning cache
	m_skinningCache = ruNEW CRuHashMap<CRuGUID, CachedSkin>(1024);

	// Create 2D toolkit texture atlas
	m_ru2D_TextureAtlas = ruNEW CRuTextureAtlas_Aggregate();

	m_ru2D_32BitAtlasTexture = g_ruResourceManager->CreateTexture("ruSCENERENDERER_ATLAS_32BIT", 1024, 1024, 1, ruSURFACEFMT_A8R8G8B8);

	if(m_ru2D_32BitAtlasTexture)
	{
		CRuTextureAtlas_Paged *ddsAtlas = ruNEW CRuTextureAtlas_Paged();
		ddsAtlas->Initialize(m_ru2D_32BitAtlasTexture, 256, 256, 0, 0);
		m_ru2D_TextureAtlas->BindSubAtlas(ddsAtlas);
		ruSAFE_RELEASE(ddsAtlas);
	}

	m_ru2D_32BitAtlasTexture_Text = g_ruResourceManager->CreateTexture("ruSCENERENDERER_ATLAS_32BIT_TEXT", 1024, 1024, 3, ruSURFACEFMT_A8R8G8B8);
//	m_ru2D_32BitAtlasTexture_Text = g_ruResourceManager->CreateTexture("ruSCENERENDERER_ATLAS_32BIT_TEXT", 2048, 1024, 1, ruSURFACEFMT_A8R8G8B8);

	if(m_ru2D_32BitAtlasTexture_Text)
	{
		CRuTextureAtlas_Paged *ddsAtlas = ruNEW CRuTextureAtlas_Paged();
		ddsAtlas->Initialize(m_ru2D_32BitAtlasTexture_Text, 256, 256, 1024, 1024);
		m_ru2D_TextureAtlas->BindSubAtlas(ddsAtlas);
		ruSAFE_RELEASE(ddsAtlas);
	}

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

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

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

	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_2SIDED, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_FACETED, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_WIRE, FALSE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_ALPHATESTMODE, TRUE);
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_MATERIAL_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);

	// Reverse cull disabled by default
	m_defaultParamCache.CacheParameter(ruPARAMTYPE_FX_REVERSECULL, FALSE);

/*
	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;

	// Set up profiler probes
	m_probe_2D_Submit = g_ruProfiler->CreateProbe("2D Submit", "Scene");
	m_probe_2D_BatchSize = g_ruProfiler->CreateProbe("2D Batch Size", "Scene");
	m_probe_2D_BatchCount = g_ruProfiler->CreateProbe("2D Batches", "Scene");
	m_probe_2D_Flush = g_ruProfiler->CreateProbe("2D Flush", "Scene");
	m_probe_3D_Submit = g_ruProfiler->CreateProbe("3D Submit", "Scene");
	m_probe_3D_BatchCount = g_ruProfiler->CreateProbe("3D Batches", "Scene");
	m_probe_3D_Flush = g_ruProfiler->CreateProbe("3D Flush", "Scene");
	m_probe_3D_Skinning = g_ruProfiler->CreateProbe("Skinning", "Scene");
	m_probe_3D_Morphing = g_ruProfiler->CreateProbe("Morphing", "Scene");
	m_probe_3D_Sorting = g_ruProfiler->CreateProbe("3D Sorting", "Scene");
	m_probe_3D_Parameters = g_ruProfiler->CreateProbe("3D Parameters", "Scene");
	m_probe_3D_Lighting = g_ruProfiler->CreateProbe("3D Lighting", "Scene");
	m_probe_3D_TransformSetup = g_ruProfiler->CreateProbe("Transform Setup", "Scene");
	m_probe_3D_TextureSetup = g_ruProfiler->CreateProbe("Texture Setup", "Scene");
	m_probe_3D_RenderAllPasses = g_ruProfiler->CreateProbe("3D Render Passes", "Scene");
	m_probe_3D_Render = g_ruProfiler->CreateProbe("3D Render", "Scene");
	m_probe_3D_RenderD3D = g_ruProfiler->CreateProbe("3D Render D3D", "Scene");

	m_probe_Engine_InstancingTime = g_ruProfiler->CreateProbe("Engine Instancing Time", "Scene");

	m_probe_Misc_AntiPortalDiscarded = g_ruProfiler->CreateProbe("Anti-Portal Discarded", "Scene");
}

CRuSceneRenderer::~CRuSceneRenderer()
{
	// Delete all registered event listener descriptors
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		// Advance to next listener descriptor and delete
		EventListenerDesc *deletedListener = curListener;
		curListener = curListener->m_next;
		delete deletedListener;
	}

	// Clear event listeners
	m_eventListeners = NULL;

	// Flush the skinning cache
	FlushSkinningCache();

	// Release the skinning cache
	delete m_skinningCache;

	// Release 2D reference material
	ruSAFE_RELEASE(m_ru2D_RefMaterial);

	// Release the NULL texture
	ruSAFE_RELEASE(m_nullTexture);

	// Release the clamp texture
	ruSAFE_RELEASE(m_clampTexture);

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

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

	ruSAFE_RELEASE(m_ru2D_32BitAtlasTexture);
	ruSAFE_RELEASE(m_ru2D_32BitAtlasTexture_Text);

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

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

	for(INT32 i = 0; i < m_ru2D_ProxyMeshes.Count(); ++i)
	{
		ruSAFE_RELEASE(m_ru2D_ProxyMeshes[i]);
	}

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

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

	// Release material stack
	Ru2D_FlushMaterialStack();
	while(m_unusedMaterialStack.IsEmpty() == FALSE)
	{
		CRuMaterial *material = m_unusedMaterialStack.Pop();

		DWORD instanceDataParam;
		material->GetParameterBlock()->GetParameter(ruPARAMTYPE_GEOINSTANCE_S_DATA, instanceDataParam);
		delete[] reinterpret_cast<REAL *>(instanceDataParam);

		ruSAFE_RELEASE(material);
	}

	// 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();
}

BOOL CRuSceneRenderer::RegisterListener(IRuEntityEventListener *listener, void *userData)
{
	EventListenerDesc *listenerDesc = ruNEW EventListenerDesc();
	listenerDesc->m_listener = listener;
	listenerDesc->m_userData = userData;
	listenerDesc->m_next = NULL;

	if(m_eventListeners == NULL)
	{
		m_eventListeners = listenerDesc;
	}
	else
	{
		EventListenerDesc *curListener = m_eventListeners;
		while(curListener->m_next)
		{
			// Advance to next listener
			curListener = curListener->m_next;
		}

		curListener->m_next = listenerDesc;
	}

	return TRUE;
}

BOOL CRuSceneRenderer::UnregisterListener(IRuEntityEventListener *listener)
{
	EventListenerDesc *prevListener = NULL;
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		if(curListener->m_listener == listener)
		{
			if(prevListener)
			{
				prevListener->m_next = curListener->m_next;
			}
			else
			{
				m_eventListeners = curListener->m_next;
			}

			delete curListener;

			return TRUE;
		}

		// Advance to next listener
		prevListener = curListener;
		curListener = curListener->m_next;
	}

	return FALSE;
}

/*!
	Raises an event with the specified source entity, event ID, and event parameter.

	Note that the entity event functions are not reentrant. Listeners must never
	modify the listener list during a signaled event. The only exception is if
	the listener removes itself from the list of listeners. This operation is
	supported to allow listeners to properly unregister during disposal.

	@param entity Entity that raised the event.
	@param eventProxy Proxy object that actually raised the event, or NULL.
	@param eventID Event ID.
	@param eventParameter0 Event parameter 0.
	@param eventParameter1 Event parameter 1.
	@param eventString Event string.
	@param eventDuration Event duration.

	@return None.
*/
void CRuSceneRenderer::RaiseEvent(CRuEntity *entity, IRuObject *eventProxy, INT32 eventID, PTRVALUE eventParameter0, PTRVALUE eventParameter1, const char *eventString, REAL eventDuration, BOOL propagateEvent)
{
	// Trigger any event listeners registered to this entity
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		IRuEntityEventListener *listener = curListener->m_listener;
		void *userData = curListener->m_userData;

		// Advance to next listener
		curListener = curListener->m_next;

		// If the listener handles the event, return immediately
		if(listener->HandleEvent(entity, eventProxy, eventID, eventParameter0, eventParameter1, eventString, eventDuration, userData))
		{
			if(eventID >= ruENTITYEVENT_BROADCAST_CAMERA)
			{
			}
			else
			{
				return;
			}
		}
	}
}

void CRuSceneRenderer::EnableRendering(BOOL enableRendering)
{
	m_enableRendering = enableRendering;
}

void CRuSceneRenderer::Debug_ToggleNullRendering()
{
	m_debug_NullRendering = !m_debug_NullRendering;
}

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;
}

CRuCamera *CRuSceneRenderer::GetCamera()
{
	return m_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::Begin()
{
	// 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);
		}
	}

	if(m_clampTexture == NULL)
	{
		m_clampTexture = g_ruResourceManager->CreateTexture("RUSYSTEM_CLAMP", 128, 128, 1, ruSURFACEFMT_A8R8G8B8);
		assert(m_clampTexture);

		DWORD *pixels;
		INT32 pitch;

		if(m_clampTexture->Lock(0, (void **) &pixels, &pitch))
		{
			for(INT32 z = 0; z < 128; ++z)
			{
				for(INT32 x = 0; x < 128; ++x)
				{
					if(z == 0 || x == 0 || z == 127 || x == 127)
						pixels[x + z * pitch / 4] = 0x00000000;
					else
						pixels[x + z * pitch / 4] = 0xFFFFFFFF;
				}
			}

			m_clampTexture->Unlock(0);
		}
	}

	// 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);
	}

	// Clear the RT stack
	m_ru2D_RTStack.Clear();

	// Reset next stencil ID
	m_nextStencilID = 0;

	// Clear the material override callback push stack
	m_materialOverrideCallbackStack.Clear();

	// Clear the current material override callback
	m_materialOverrideCallback = NULL;

	// Clear the render target push stack
	m_rtStack.Clear();

	// Clear the currently selected render target and viewport
	m_primaryRTIndex = -1;
	m_curRTIndex = -1;
	m_curViewport = -1;

	// Clear the currently selected clip plane set index
	m_curCPSetIndex = -1;

	// Reset object ID
	m_nextObjectID = 0;

	m_scene_FlushIdx = 0;

	// Reset selected RT and viewport indices
	m_selectedRTIndex = -1;
	m_selectedViewportIndex = -1;

	m_selectedCPSetIndex = -1;

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

	// Clear the list of viewports
	m_viewports.Clear();

	// Clear the list of clip planes
	m_clipPlaneSets.Clear();

	// Clear the queue of additionally applied parameter blocks
	m_extraParameterBlocks.Clear();

	// Clear the command queue
	m_commandQueue.Clear();

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

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

	// Clear anti-portal list
	m_antiPortals.Clear();

	// Clear matrix stack
	FlushMatrixStack();

	// Clear parameter cache stack
	FlushParameterCacheStack();

	// Clear material stack
	Ru2D_FlushMaterialStack();

	// Clear 2D viewport size stack
	m_ru2D_Viewport_Width_Stack.Clear();
	m_ru2D_Viewport_Height_Stack.Clear();

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

	// Increment eviction marker on the 2D texture atlas to allow eviction of old textures
	m_ru2D_TextureAtlas->IncrementEvictionMarker();

	// Reset render target pools
	g_ruResourceManager->ResetRenderTargetPools();
	g_ruResourceManager->ResetRenderTargetViews();

	// Garbage collect the skinning cache
	GarbageCollectSkinningCache();

	// Set initial renderer state to retained 3D mode
	m_rendererState = ruRENDERERSTATE_3D;

	// Notify listeners that the render has begun
	this->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_BEGIN, reinterpret_cast<PTRVALUE>(this), NULL, NULL, 0.0f, FALSE);

/*
	// Test code
	static INT32 counter = 0;
	++counter;
	if(counter % 120 == 0)
	{
		D3DXSaveTextureToFile("d:\\32bit_atlas.png", D3DXIFF_PNG, ((CRuTextureD3D *) m_ru2D_32BitAtlasTexture_Text)->GetIDirect3DTexture(), NULL);
	}
*/
}

void CRuSceneRenderer::End()
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_STOPPED);

	// Execute the command queue
	ExecuteCommandQueue();
/*
	// NOTE: This still has some problems apparently... causes problems with
	//       the portrait display.... portrait gets placed in the wrong place
	//       I only use this when rendering the minimap, so.... we'll see

	// Run a clear on all render targets which have not been cleared
	for(INT32 i = 0; i < m_renderTargets.Count(); ++i)
	{
		RenderTargetDescriptor &rtDesc = m_renderTargets[i];
		ViewportDescriptor &vpDesc = m_viewports[rtDesc.m_viewportIndex];

		if(vpDesc.m_cleared == FALSE)
		{
			// Select render target
			CommandQueueEntry &command0 = Scene_AllocateCommandQueueEntry();
			command0.m_command = ruRENDERERCMD_RENDERTARGET_SET;
			command0.RENDERTARGETSET.m_renderTarget = rtDesc.m_renderTarget;

			// Select viewport
			CommandQueueEntry &command1 = Scene_AllocateCommandQueueEntry();
			command1.m_command = ruRENDERERCMD_VIEWPORT_SET;
			command1.VIEWPORTSET.m_viewport = vpDesc.m_viewport;

			// Clear viewport
			CommandQueueEntry &command2 = Scene_AllocateCommandQueueEntry();
			command2.m_command = ruRENDERERCMD_VIEWPORT_CLEAR;
			command2.VIEWPORTCLEAR.m_clearFlags = vpDesc.m_clearFlags;
			command2.VIEWPORTCLEAR.m_clearColor = vpDesc.m_clearColor;
			command2.VIEWPORTCLEAR.m_clearZ = vpDesc.m_clearZ;
			command2.VIEWPORTCLEAR.m_clearStencil = vpDesc.m_clearStencil;

			// Mark viewport as cleared
			vpDesc.m_cleared = TRUE;
		}
	}

	ExecuteCommandQueue();
*/
	//	Scene_Flush();

	// Accumulate batch count
	g_ruProfiler->AccumulateProbeValue(m_probe_3D_BatchCount, static_cast<REAL>(m_numBatchesIssued));

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

	// Notify listeners that the render has ended
	this->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_END, reinterpret_cast<PTRVALUE>(this), NULL, NULL, 0.0f, FALSE);
}

UINT8 CRuSceneRenderer::GetNextStencilID()
{
	++m_nextStencilID;
	return m_nextStencilID;
}

BOOL CRuSceneRenderer::PushRenderTarget(IRuObject *renderTarget)
{
	// Transition to 2D rendering mode
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Flush any 2D batches
	Ru2D_Flush();

	// Push current render target onto stack
	m_ru2D_RTStack.Push(m_ru2D_CurrentRenderTarget);

	// Store reference to new render target
	m_ru2D_CurrentRenderTarget = renderTarget;

	// Activate the render target
//	return SelectRenderTarget(m_ru2D_CurrentRenderTarget);

	// Update reference RT width
	// Select the render target
	if(renderTarget->GetType().IsSubClassOf(IRuSurface::Type))
	{
		m_ru2D_RefRTWidth = static_cast<IRuSurface *>(renderTarget)->GetWidth();
		m_ru2D_RefRTHeight = static_cast<IRuSurface *>(renderTarget)->GetHeight();
	}
	else if(renderTarget->GetType().IsSubClassOf(IRuSwapChain::Type))
	{
		m_ru2D_RefRTWidth = static_cast<IRuSwapChain *>(renderTarget)->GetWidth();
		m_ru2D_RefRTHeight = static_cast<IRuSwapChain *>(renderTarget)->GetHeight();
	}
	else if(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
		m_ru2D_RefRTWidth = static_cast<IRuTexture *>(renderTarget)->GetLevelWidth(0);
		m_ru2D_RefRTHeight = static_cast<IRuTexture *>(renderTarget)->GetLevelHeight(0);
	}

	// Insert render target set command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_RENDERTARGET_SET;
	command.RENDERTARGETSET.m_renderTarget = m_ru2D_CurrentRenderTarget;

	return TRUE;
}

BOOL CRuSceneRenderer::PopRenderTarget()
{
	// Transition to 2D rendering mode
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Flush any 2D batches
	Ru2D_Flush();

	if(m_ru2D_RTStack.IsEmpty() == FALSE)
	{
		m_ru2D_CurrentRenderTarget = m_ru2D_RTStack.Pop();

		if(m_ru2D_CurrentRenderTarget)
		{
			// Insert render target set command
			CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

			command.m_command = ruRENDERERCMD_RENDERTARGET_SET;
			command.RENDERTARGETSET.m_renderTarget = m_ru2D_CurrentRenderTarget;

			return TRUE;

//			return SelectRenderTarget(m_ru2D_CurrentRenderTarget);
		}

		return FALSE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Ru2D_PushViewport(IRuViewport *viewport)
{
	// Transition to 2D rendering mode
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Flush any 2D batches
	Ru2D_Flush();

	// Push current viewport onto stack
	m_ru2D_ViewportStack.Push(m_ru2D_CurrentViewport);

	// Store reference to new viewport
	m_ru2D_CurrentViewport = viewport;
	
	// Select viewport
//	m_ru2D_CurrentViewport->SelectAsViewport();

	// Insert viewport set command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_VIEWPORT_SET;
	command.VIEWPORTSET.m_viewport = m_ru2D_CurrentViewport;

	// Update reference RT width
	m_ru2D_RefRTWidth = viewport->GetViewportWidth();
	m_ru2D_RefRTHeight = viewport->GetViewportHeight();

	return TRUE;
}

BOOL CRuSceneRenderer::Ru2D_PopViewport()
{
	// Transition to 2D rendering mode
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Flush any 2D batches
	Ru2D_Flush();

	if(m_ru2D_ViewportStack.IsEmpty() == FALSE)
	{
		m_ru2D_CurrentViewport = m_ru2D_ViewportStack.Pop();

		if(m_ru2D_CurrentViewport)
		{
			// Select viewport
//			m_ru2D_CurrentViewport->SelectAsViewport();

			// Insert viewport set command
			CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

			command.m_command = ruRENDERERCMD_VIEWPORT_SET;
			command.VIEWPORTSET.m_viewport = m_ru2D_CurrentViewport;
		}
		else
		{
			// Reset viewport
//			g_ruEngine->ResetViewport();

			// Insert viewport set command
			CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

			command.m_command = ruRENDERERCMD_VIEWPORT_SET;
			command.VIEWPORTSET.m_viewport = NULL;
		}

		// Update reference render target size
		g_ruEngine->GetRenderTargetSize(m_ru2D_RefRTWidth, m_ru2D_RefRTHeight);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Ru2D_ClearViewport(DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	// Transition to 2D rendering mode
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Clear currently active viewport
//	g_ruEngine->Clear(clearFlags, color, z, stencil);

	// Insert viewport clear command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_VIEWPORT_CLEAR;
	command.VIEWPORTCLEAR.m_clearFlags = clearFlags;
	command.VIEWPORTCLEAR.m_clearColor = color;
	command.VIEWPORTCLEAR.m_clearZ = z;
	command.VIEWPORTCLEAR.m_clearStencil = stencil;

	return TRUE;
}

IRuEngineResource *CRuSceneRenderer::GetPrimaryRenderTarget()
{
	if(m_primaryRTIndex >= 0)
	{
		return static_cast<IRuEngineResource *>(m_renderTargets[m_primaryRTIndex].m_renderTarget);
	}

	return NULL;
}

INT32 CRuSceneRenderer::GetPrimaryRenderTargetWidth()
{
	if(m_primaryRTIndex >= 0)
	{
		IRuObject *primaryRT = m_renderTargets[m_primaryRTIndex].m_renderTarget;

		if(primaryRT->GetType().IsSubClassOf(IRuSurface::Type))
		{
			return static_cast<IRuSurface *>(primaryRT)->GetWidth();
		}
		else if(primaryRT->GetType().IsSubClassOf(IRuSwapChain::Type))
		{
			return static_cast<IRuSwapChain *>(primaryRT)->GetWidth();
		}
		else if(primaryRT->GetType().IsSubClassOf(IRuTexture::Type))
		{
			return static_cast<IRuTexture *>(primaryRT)->GetLevelWidth(0);
		}
	}

	return 0;
}

INT32 CRuSceneRenderer::GetPrimaryRenderTargetHeight()
{
	if(m_primaryRTIndex >= 0)
	{
		IRuObject *primaryRT = m_renderTargets[m_primaryRTIndex].m_renderTarget;

		if(primaryRT->GetType().IsSubClassOf(IRuSurface::Type))
		{
			return static_cast<IRuSurface *>(primaryRT)->GetHeight();
		}
		else if(primaryRT->GetType().IsSubClassOf(IRuSwapChain::Type))
		{
			return static_cast<IRuSwapChain *>(primaryRT)->GetHeight();
		}
		else if(primaryRT->GetType().IsSubClassOf(IRuTexture::Type))
		{
			return static_cast<IRuTexture *>(primaryRT)->GetLevelHeight(0);
		}
	}

	return 0;
}

BOOL CRuSceneRenderer::Scene_PushMaterialOverrideCallback(IRuSceneRenderer::MaterialOverrideCallback overrideCallback)
{
	// Push previous callback onto push stack
	m_materialOverrideCallbackStack.Push(m_materialOverrideCallback);

	// Set new callback
	m_materialOverrideCallback = overrideCallback;

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PopMaterialOverrideCallback()
{
	// Assert on stack empty
	assert(m_materialOverrideCallbackStack.IsEmpty() == FALSE);

	// Pop previous callback from push stack
	m_materialOverrideCallback = m_materialOverrideCallbackStack.Pop();

	return TRUE;
}

void CRuSceneRenderer::Scene_SetRenderTarget(IRuObject *renderTarget, INT32 priority, DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	Scene_PushRenderTarget(renderTarget, priority, clearFlags, color, z, stencil);
}

INT32 CRuSceneRenderer::CreateViewport(IRuViewport *viewport, DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	INT32 viewportIndex = -1;

	// Attempt to find the viewport in the list of currently used viewports
	if(viewport != NULL)
	{
		for(INT32 i = 0; i < m_viewports.Count(); ++i)
		{
			// If the viewport is already in the list, used the existing viewport
			if(m_viewports[i].m_viewport == viewport)
			{
				viewportIndex = i;
				break;
			}
		}
	}

	if(viewportIndex == -1)
	{
		// Build the render target descriptor
		ViewportDescriptor viewportDescriptor;
		viewportDescriptor.m_viewport = viewport;
		viewportDescriptor.m_cleared = FALSE;

		viewportDescriptor.m_clearFlags = clearFlags;
		viewportDescriptor.m_clearColor = color;
		viewportDescriptor.m_clearZ = z;
		viewportDescriptor.m_clearStencil = stencil;

		// Add descriptor to list
		m_viewports.Add(viewportDescriptor);

		// Store viewport index
		viewportIndex = m_viewports.Count() - 1;
	}

	// Return viewport index
	return viewportIndex;
}

BOOL CRuSceneRenderer::Scene_PushRenderTarget(IRuObject *renderTarget, INT32 priority, DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	// Transition to 3D rendering mode
	TransitionRendererState(ruRENDERERSTATE_3D);

	IRuViewport *viewportOverride = NULL;

	// Push previous render target and viewport index onto push stack
	m_rtStack.Push(RTVPTuple(m_curRTIndex, m_curViewport));

	// Reset current render target index
	m_curRTIndex = -1;
	m_curViewport = -1;

	// Operate based on render target type
	if(renderTarget->GetType().IsSubClassOf(IRuTextureView::Type))
	{
		IRuTextureView *textureView = static_cast<IRuTextureView *>(renderTarget);

		if(textureView)
		{
			renderTarget = textureView->GetTexture();
			viewportOverride = textureView->GetViewport();
		}
	}

	// Abort if necessary
	if(renderTarget == NULL)
	{
		return FALSE;
	}

	// 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;
			m_curViewport = m_renderTargets[i].m_viewportIndex;
			break;
		}
	}

	// If the render target is new, create a new descriptor for it
	if(m_curRTIndex < 0)
	{
		// Build the render target descriptor
		RenderTargetDescriptor rtDescriptor;
		rtDescriptor.m_renderTarget = renderTarget;
		rtDescriptor.m_depthStencilSurface = NULL;
		rtDescriptor.m_priority = priority;
		rtDescriptor.m_viewportIndex = CreateViewport(NULL, clearFlags, color, z, stencil);;
		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;
		m_curViewport = rtDescriptor.m_viewportIndex;
	}

	// If a viewport override is specified, apply it now
	if(viewportOverride)
	{
		m_curViewport = CreateViewport(viewportOverride, clearFlags, color, z, stencil);
	}

	if(m_curRTIndex == 0)
	{
		m_primaryRTIndex = m_curRTIndex;
	}

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PopRenderTarget()
{
	// Transition to 3D rendering mode
	TransitionRendererState(ruRENDERERSTATE_3D);

	if(m_rtStack.IsEmpty() == FALSE)
	{
		RTVPTuple rtvpTuple = m_rtStack.Pop();

		m_curRTIndex = rtvpTuple.m_rtIndex;
		m_curViewport = rtvpTuple.m_vpIndex;

		assert(m_curRTIndex != -1 && m_curViewport != -1);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Scene_PushViewport(IRuViewport *viewport, DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	// Transition to 3D rendering mode
	TransitionRendererState(ruRENDERERSTATE_3D);

	// Push previous render target and viewport index onto push stack
	m_rtStack.Push(RTVPTuple(m_curRTIndex, m_curViewport));

	// Reset current viewport index
	m_curViewport = CreateViewport(viewport, clearFlags, color, z, stencil);

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PopViewport()
{
	// Transition to 3D rendering mode
	TransitionRendererState(ruRENDERERSTATE_3D);

	if(m_rtStack.IsEmpty() == FALSE)
	{
		RTVPTuple rtvpTuple = m_rtStack.Pop();

		m_curRTIndex = rtvpTuple.m_rtIndex;
		m_curViewport = rtvpTuple.m_vpIndex;

		assert(m_curRTIndex != -1 && m_curViewport != -1);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Scene_PushClipPlane(INT32 index, const CRuPlane4 &plane)
{
	// Push previous clip plane set index onto stack
	m_clipPlaneStack.Push(m_curCPSetIndex);

	// Create a descriptor for the new clip plane set
	ClipPlaneSet cpSet;

	if(m_clipPlaneSets.Count())
	{
		cpSet = m_clipPlaneSets[m_clipPlaneSets.Count() - 1];
	}

	cpSet.m_clipPlanes[index] = plane;

	// Insert clip plane set into clip plane set list and set current clip plane set index
	m_curCPSetIndex = m_clipPlaneSets.Count();
	m_clipPlaneSets.Add(cpSet);

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PopClipPlane(INT32 index)
{
	if(m_clipPlaneStack.IsEmpty())
	{
		m_curCPSetIndex = -1;
	}
	else
	{
		m_curCPSetIndex = m_clipPlaneStack.Pop();
	}

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PushParameterBlock(CRuParameterBlock *paramBlock)
{
	m_extraParameterBlocks.Add(paramBlock);

	return TRUE;
}

BOOL CRuSceneRenderer::Scene_PopParameterBlock()
{
	if(m_extraParameterBlocks.Count())
	{
		m_extraParameterBlocks.RemoveAt(m_extraParameterBlocks.Count() - 1);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSceneRenderer::Scene_PushEventSuppression(RuEntityEvent suppressedEvent)
{
	m_eventSuppressionList.Add(suppressedEvent);

	return TRUE;
}

RuEntityEvent CRuSceneRenderer::Scene_PopEventSuppression()
{
	if(m_eventSuppressionList.Count() > 0)
	{
		RuEntityEvent poppedEvent = m_eventSuppressionList[m_eventSuppressionList.Count() - 1];

		m_eventSuppressionList.RemoveAt(m_eventSuppressionList.Count() - 1);

		return poppedEvent;
	}

	return ruENTITYEVENT_NONE;
}

void CRuSceneRenderer::Scene_Submit(CRuEntity *sceneRoot, CRuCamera *camera, BOOL noStatusMod)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_3D);

	// 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;
		}
	}

	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Submit, 2);

	// 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);

	BOOL eventSurpressed_SceneSubmit = IsEventSuppressed(ruENTITYEVENT_BROADCAST_SUBMITTED);

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

		// Notify attached listeners that an entity is being submitted for rendering
		if(eventSurpressed_SceneSubmit == FALSE)
		{
			this->RaiseEvent(curEntity, NULL, ruENTITYEVENT_BROADCAST_SUBMIT, reinterpret_cast<PTRVALUE>(this), reinterpret_cast<PTRVALUE>(camera), NULL, 0.0f, FALSE);
		}



		BOOL backdropEntity = curEntity->GetOption(ruENTITYOPTION_BACKDROP);
		BOOL ignoreVisibility = curEntity->GetOption(ruENTITYOPTION_IGNOREVISIBILITY) | curEntity->GetOption(ruENTITYOPTION_BUDISABLE);

		// Check for visibility in frustum
		if(!ignoreVisibility)
		{
			if(!backdropEntity)
			{
				if(IntersectsFrustum(camera->GetFrustumPlanes(), curEntity->GetWorldBounds()) == FALSE)
				{
					if(!noStatusMod)
						curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, FALSE);
					continue;
				}
			}
			else
			{
				if(IntersectsFrustum(camera->GetBackdropFrustumPlanes(), curEntity->GetWorldBounds()) == FALSE)
				{
					if(!noStatusMod)
						curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, FALSE);
					continue;
				}
			}
		}

		// Mark the entity as visible
		if(!noStatusMod)
			curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, TRUE);

		// If the entity is marked hidden, skip
		if(curEntity->GetOption(ruENTITYOPTION_HIDDEN) || curEntity->GetOption(ruENTITYOPTION_HIDDENBYDISTANCE))
		{
			continue;
		}

		// Broadcast scene submit event to all listeners
		if(eventSurpressed_SceneSubmit == FALSE)
		{
			RuBroadcast_Submitted_Args eventArgs = { this, camera, TRUE };

			// Notify entity that it has been submitted for rendering
//			curEntity->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

			// Notify attached listeners that an entity that it has been submitted for rendering
			this->RaiseEvent(curEntity, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

			// If we are not to continue with this submission, abort
			if(eventArgs.m_continueSubmission == FALSE)
			{
				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 a subclass of the type anti-portal, insert it into the list of anti-portals
		if(curEntity->GetType().IsSubClassOf(CRuEntity_AntiPortal::Type))
		{
			m_antiPortals.Add(static_cast<CRuEntity_AntiPortal *>(curEntity));

			// Broadcast acting camera to the entity
			BOOL eventSurpressed_Broadcast_Camera = IsEventSuppressed(ruENTITYEVENT_BROADCAST_CAMERA);

			if(eventSurpressed_Broadcast_Camera == FALSE)
			{
//				curEntity->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_CAMERA, reinterpret_cast<PTRVALUE>(camera), 0, NULL, 0.0f, FALSE);
			}
		}

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

			// Create a render queue entry for every mesh in the renderable entity
			for(INT32 i = 0, numMeshes = renderableEntity->GetNumMeshes(); i < numMeshes; ++i)
			{
				IRuMesh *mesh;
				IRuMaterial *material;

				// Get the i-th mesh and material
				if(renderableEntity->GetMesh(i, &mesh, &material) == FALSE)
				{
					assert(FALSE);
					m_renderQueue.SetNumEntries(m_renderQueue.Count() - 1);
				}

				Scene_Submit_Internal(renderableEntity, NULL, mesh, material, camera, i == 0);
			}
		}
/*
		assert(!(reinterpret_cast<DWORD>(curEntity->GetResourceName()) > 0 && reinterpret_cast<DWORD>(curEntity->GetResourceName()) < 0x1000000));
		assert(!(reinterpret_cast<DWORD>(curEntity->GetObjectName()) > 0 && reinterpret_cast<DWORD>(curEntity->GetObjectName()) < 0x1000000));
		assert(!(reinterpret_cast<DWORD>(curEntity->GetFirstChild()) > 0 && reinterpret_cast<DWORD>(curEntity->GetFirstChild()) < 0x1000000));
		assert(!(reinterpret_cast<DWORD>(curEntity->GetPreviousSibling()) > 0 && reinterpret_cast<DWORD>(curEntity->GetPreviousSibling()) < 0x1000000));
		assert(!(reinterpret_cast<DWORD>(curEntity->GetNextSibling()) > 0 && reinterpret_cast<DWORD>(curEntity->GetNextSibling()) < 0x1000000));
*/
	}
/*
	// Notify entity that it has been submitted for rendering
	if(IsEventSuppressed(ruENTITYEVENT_BROADCAST_SUBMITTED) == FALSE)
	{
		sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(this), reinterpret_cast<PTRVALUE>(camera), NULL, 0.0f, FALSE);
	}
*/
	RUPROFILE_SAMPLE_END(m_probe_3D_Submit, 2);
}

void CRuSceneRenderer::Scene_Submit(CRuEntity *sceneRoot, IRuMesh *mesh, IRuMaterial *material)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_3D);

	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Submit, 2);

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

	BOOL eventSurpressed_SceneSubmit = IsEventSuppressed(ruENTITYEVENT_BROADCAST_SUBMITTED);
	BOOL abortSubmission = FALSE;

	// Broadcast scene submit event to all listeners
	if(eventSurpressed_SceneSubmit == FALSE)
	{
		RuBroadcast_Submitted_Args eventArgs = { this, m_camera, TRUE };

		// Notify entity that it has been submitted for rendering
//		sceneRoot->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

		// Notify attached listeners that an entity that it has been submitted for rendering
		this->RaiseEvent(sceneRoot, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

		// If we are not to continue with this submission, abort
		if(eventArgs.m_continueSubmission == FALSE)
		{
			abortSubmission = TRUE;
		}
	}

	if(!abortSubmission)
	{
		// Submit
		Scene_Submit_Internal(sceneRoot, NULL, mesh, material, m_camera, TRUE);
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_Submit, 2);
}

void CRuSceneRenderer::Scene_Submit_Internal(CRuEntity *sceneRoot, CRuMatrix4x4 const *worldTransform, IRuMesh *mesh, IRuMaterial *material, CRuCamera *camera, BOOL firstEntityInSeries)
{
	INT32 billboardType = ruBILLBOARDTYPE_NONE;
	BOOL isBackdrop = FALSE;

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

	if(camera == NULL)
		camera = m_camera;

	if(worldTransform == NULL)
	{
		if(sceneRoot)
		{
			worldTransform = &sceneRoot->GetWorldTransform();
		}
		else
		{
			worldTransform = &m_identityTransform;
		}
	}

	RenderQueueEntry &rqEntry = Scene_AllocateRenderQueueEntry();

	rqEntry.m_rtIndex = m_curRTIndex;
	rqEntry.m_viewport = m_curViewport;
	rqEntry.m_cpSetIndex = m_curCPSetIndex;
	rqEntry.m_worldTransform = worldTransform;
	rqEntry.m_paramCache = CreateParameterCache();

	rqEntry.m_noReplication = FALSE;
	rqEntry.m_objectID = m_nextObjectID++;

	rqEntry.m_mesh = mesh;
	rqEntry.m_material = material;

	// Override material if necessary
	if(m_materialOverrideCallback)
	{
		IRuMaterial *overrideMaterial = m_materialOverrideCallback(rqEntry.m_material);
		if(overrideMaterial)
		{
			rqEntry.m_material = overrideMaterial;
		}
	}

	// Make sure material has a valid shader
	if(rqEntry.m_material->GetShader() == NULL)
	{
		Scene_UndoAllocateRenderQueueEntry();
		return;
	}


	// debugging
	rqEntry.m_entity = sceneRoot;
	rqEntry.m_mesh->AddRef();
	rqEntry.m_material->AddRef();


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

	// blah?
	m_parameterBlocks.Clear();

	// Additional parameter blocks
	for(INT32 i = 0; i < m_extraParameterBlocks.Count(); ++i)
	{
		m_parameterBlocks.Queue(m_extraParameterBlocks[i]);
	}

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

	if(sceneRoot)
	{
		// Find all parameter blocks and queue them into the parameter block queue
		CRuEntity *curEntity = sceneRoot;
		while(curEntity != 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(curEntity->GetParameterBlock());

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

		// Set lighting information into the shader (TODO: Only do this if required by shader)
		RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Lighting, 2);

		LightingInfo *lightingInfo = BuildLightingInfo(static_cast<IRuEntity_Renderable *>(sceneRoot));

		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_AMBIENT, (DWORD) &lightingInfo->m_ambientLight);
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_OBJ_SH9, (DWORD) lightingInfo->m_shCoefficients);

		// Set specular lighting parameters
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_DIR, (DWORD) &lightingInfo->m_specularDirection);
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_COLOR, (DWORD) m_specularColor);

		RUPROFILE_SAMPLE_END(m_probe_3D_Lighting, 2);

		// Get the entity's billboard type
		billboardType = sceneRoot->GetOption(ruENTITYOPTION_BILLBOARD);

		// Cache the projection transform
		isBackdrop = sceneRoot->GetOption(ruENTITYOPTION_BACKDROP);

		// Broadcast events
		if(firstEntityInSeries)
		{
			BOOL eventSurpressed_Broadcast_Camera = IsEventSuppressed(ruENTITYEVENT_BROADCAST_CAMERA);
			BOOL eventSurpressed_Broadcast_DecalTarget = IsEventSuppressed(ruENTITYEVENT_BROADCAST_DECALTARGET);

			// Broadcast acting camera to the entity
			if(eventSurpressed_Broadcast_Camera == FALSE)
			{
//				sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_CAMERA, reinterpret_cast<PTRVALUE>(camera), 0, NULL, 0.0f, FALSE);
			}

			// Decal handling -- broadcast valid decal target array to the entity
			if(eventSurpressed_Broadcast_DecalTarget == FALSE)
			{
//				sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_DECALTARGET, reinterpret_cast<PTRVALUE>(&m_validDecalTargets), 0, NULL, 0.0f, FALSE);
			}
		}

		// If the mesh is skinned and there is a hierarchy available, skin it
		if(sceneRoot->GetOption(ruENTITYOPTION_SKINNED))
		{
			// Find the frame hierarchy to use for this entity ...
			CRuFrameHierarchy *frameHierarchy = NULL;
			CRuHierarchy_GR2 *hierarchy_GR2 = NULL;
			CRuEntity *parentEntity = sceneRoot->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(hierarchy_GR2 == NULL && parentEntity->GetType().IsSubClassOf(CRuHierarchy_GR2::Type))
				{
					hierarchy_GR2 = static_cast<CRuHierarchy_GR2 *>(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();
			}

			if(frameHierarchy || hierarchy_GR2)
			{
				CachedSkin cachedSkin;

				CRuGUID meshKey = rqEntry.m_mesh->GetGUID();
				DWORD meshKeyHash = meshKey.HashCode();

				// Enable skinning
				rqEntry.m_material->SetSkinned(TRUE);

				// Locate the cached skin mesh for the current entry
				if(m_skinningCache->Get(meshKey, meshKeyHash, cachedSkin) == FALSE)
				{
//					DWORD t0 = timeGetTime();

					if(rqEntry.m_mesh->GetType() == CRuMesh::Type)
					{
						// Set skinnable mesh to NULL
						cachedSkin.m_mesh = NULL;

						// Set last update time
						cachedSkin.m_lastUpdateTime = timeGetTime();

						// Subdivide mesh
						cachedSkin.m_subdividedMeshes = ruNEW CRuArrayList<IRuMesh *>();
						static_cast<CRuMesh *>(rqEntry.m_mesh)->SkinnedMesh_Subdivide(74, cachedSkin.m_subdividedMeshes);

						// Put cached skin entry into list
						m_skinningCache->Add(meshKey, meshKeyHash, cachedSkin);
					}
/*
					DWORD t1 = timeGetTime();
					DWORD et = t1 - t0;

					char outputStr[1024];
					sprintf(outputStr, "Skinned mesh split @ %d ms.\n", et);
					OutputDebugString(outputStr);
*/
				}
				else
				{
					// Set last update time
					cachedSkin.m_lastUpdateTime = timeGetTime();

					// Update cache entry
					m_skinningCache->Replace(meshKey, meshKeyHash, cachedSkin);
				}

				if(cachedSkin.m_subdividedMeshes)
				{
					RenderQueueEntry queueEntryCopy = rqEntry;

					// Queue the first subdivided mesh (there is always at least one mesh)
					ruSAFE_RELEASE(rqEntry.m_mesh);
					rqEntry.m_mesh = cachedSkin.m_subdividedMeshes->Get(0);
					rqEntry.m_mesh->AddRef();

					// Queue the remaining subdivided meshes
					for(INT32 k = 1; k < cachedSkin.m_subdividedMeshes->Count(); ++k)
					{
						RenderQueueEntry &copiedEntry = Scene_AllocateRenderQueueEntry();
						copiedEntry = queueEntryCopy;

						copiedEntry.m_mesh = cachedSkin.m_subdividedMeshes->Get(k);
						copiedEntry.m_mesh->AddRef();
						copiedEntry.m_material->AddRef();
					}
				}

				// Hierarchy stuff
				if(frameHierarchy)
				{
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_NODECOUNT, static_cast<DWORD>(frameHierarchy->GetNumSubNodes()));
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_TRANSFORMS, reinterpret_cast<DWORD>(frameHierarchy->GetSubNodeTransforms()));
				}
				else if(hierarchy_GR2)
				{
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_NODECOUNT, static_cast<DWORD>(hierarchy_GR2->GetNumSubNodes()));
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_TRANSFORMS, reinterpret_cast<DWORD>(hierarchy_GR2->GetSubNodeTransforms()));
				}
			}
		}
	}

	// Set default scene parameters into shader
	rqEntry.m_paramCache->CacheParameterBlock(&m_paramBlock);

	// 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());
	}

	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_TransformSetup, 2);

	// Cache projection transform
	if(isBackdrop)
	{
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &camera->GetBackdropProjectionTransform());
	}
	else
	{
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &camera->GetProjectionTransform());
	}

	// Check for billboarding here? And override all of the transforms?
	switch(billboardType)
	{
		// Normal entity, no billboarding
		case ruBILLBOARDTYPE_NONE:
			{
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) rqEntry.m_worldTransform);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(rqEntry.m_worldTransform), 0, NULL, 0.0f, FALSE);
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f, FALSE);
				}
			}

			break;

		// Unconstrained billboard
		case ruBILLBOARDTYPE_UNCONSTRAINED:
			{
				CRuMatrix4x4 viewBillboardingMatrix;
				viewBillboardingMatrix.SetToBillboard(camera->GetCameraTransform(), CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));

				CRuMatrix4x4 inverseTranslationMatrix;
				inverseTranslationMatrix.SetToTranslation(-(*rqEntry.m_worldTransform)._41, -(*rqEntry.m_worldTransform)._42, -(*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 translationMatrix;
				translationMatrix.SetToTranslation((*rqEntry.m_worldTransform)._41, (*rqEntry.m_worldTransform)._42, (*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 *modifiedWorldMatrix = CreateMatrix();
//				*modifiedWorldMatrix = (*rqEntry.m_worldTransform) * inverseTranslationMatrix * viewBillboardingMatrix * translationMatrix;
				*modifiedWorldMatrix = viewBillboardingMatrix * translationMatrix;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) modifiedWorldMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(modifiedWorldMatrix), 0, NULL, 0.0f, FALSE);
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f, FALSE);
				}
			}

			break;

		case ruBILLBOARDTYPE_AXIAL:
			{
				CRuVector3 billboardAxis(0.0f, 1.0f, 0.0f);
				rqEntry.m_worldTransform->TransformVector(billboardAxis);
				billboardAxis.Normalize();

				CRuVector3 forwardAxis(0.0f, 0.0f, -1.0f);
				rqEntry.m_worldTransform->TransformVector(forwardAxis);
				forwardAxis.Normalize();

				CRuMatrix4x4 viewBillboardingMatrix;
				viewBillboardingMatrix.SetToAxialBillboard(camera->GetCameraTransform(), forwardAxis, billboardAxis);

				CRuMatrix4x4 inverseTranslationMatrix;
				inverseTranslationMatrix.SetToTranslation(-(*rqEntry.m_worldTransform)._41, -(*rqEntry.m_worldTransform)._42, -(*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 translationMatrix;
				translationMatrix.SetToTranslation((*rqEntry.m_worldTransform)._41, (*rqEntry.m_worldTransform)._42, (*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 *modifiedWorldMatrix = CreateMatrix();
//				*modifiedWorldMatrix = (*rqEntry.m_worldTransform) * inverseTranslationMatrix * viewBillboardingMatrix * translationMatrix;
				*modifiedWorldMatrix = viewBillboardingMatrix * translationMatrix;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) modifiedWorldMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(modifiedWorldMatrix), 0, NULL, 0.0f, FALSE);
//					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f, FALSE);
				}
			}

			break;

		case ruBILLBOARDTYPE_AXIAL_CUSTOM:
			{
				CRuMatrix4x4 combinedWorldViewTransform = (*rqEntry.m_worldTransform) * 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;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) identityMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) aggregateMatrix);
			}

			break;
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_TransformSetup, 2);

	// Retrieve alpha value and store entity priority
	// Retrieve force alpha blend
	DWORD parameter;
	BOOL forceAlphaBlend = FALSE;
	REAL globalAlpha = 1.0f;
	BOOL invertSortOrder = FALSE;
	BOOL ignoreAlphaSort = FALSE;

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_TRANSLUCENT, parameter))
	{
		forceAlphaBlend = parameter ? TRUE : FALSE;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_ALPHA, parameter))
	{
		globalAlpha = RuCastDWORDToFloat(parameter) * globalAlpha;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_ALPHAMODIFIER, parameter))
	{
		globalAlpha = RuCastDWORDToFloat(parameter) * globalAlpha;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_SYSTEM_INVERTSORTORDER, parameter))
	{
		invertSortOrder = parameter;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_SYSTEM_IGNOREALPHASORT, parameter))
	{
		ignoreAlphaSort = parameter;
	}

	// If alpha blending is enabled, set the priority accordingly
	if((globalAlpha < 1.0f || forceAlphaBlend) && sceneRoot && !ignoreAlphaSort)
	{
		CRuVector3 entityPos(sceneRoot->GetWorldBounds().Center());
		camera->GetFrustumTransform().TransformPointProjected(entityPos);

		if(invertSortOrder)
		{
			rqEntry.m_priority = min(0.0f, -entityPos.z) - ruEPSILON;
		}
		else
		{
			rqEntry.m_priority = max(0.0f, 1.0f - entityPos.z) + ruEPSILON;
		}
	}
	else
	{
		rqEntry.m_priority = 0.0f;
	}

	// Set default alpha
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_MATERIAL_ALPHAMODE, ruALPHAMODE_NORMAL);
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_MATERIAL_ALPHA, RuCastFloatToDWORD(1.0f));

	// Store pointer to camera
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_CAMERA, reinterpret_cast<DWORD>(camera));
}

void CRuSceneRenderer::Scene_Flush_NULL()
{
}

void CRuSceneRenderer::Scene_Flush()
{
	// Notify listeners that the 3D pipeline is about to be flushed
	if(IsEventSuppressed(ruENTITYEVENT_BROADCAST_FLUSH_3D) == FALSE)
	{
		this->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_FLUSH_3D, reinterpret_cast<PTRVALUE>(this), NULL, NULL, 0.0f, FALSE);
	}

	if(m_scene_FlushIdx < m_renderQueue.Count())
	{
		// Insert flush command
		CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

		command.m_command = ruRENDERERCMD_FLUSH;
		command.FLUSH.m_flushIdx = m_scene_FlushIdx;
		command.FLUSH.m_flushCount = m_renderQueue.Count() - m_scene_FlushIdx;

		m_scene_FlushIdx = m_renderQueue.Count();
	}

	// Notify listeners that the 3D pipeline has been flushed
	if(IsEventSuppressed(ruENTITYEVENT_BROADCAST_FLUSHED_3D) == FALSE)
	{
		this->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_FLUSHED_3D, reinterpret_cast<PTRVALUE>(this), NULL, NULL, 0.0f, FALSE);
	}
}

void CRuSceneRenderer::ExecuteCommandQueue()
{
	for(INT32 cmdIdx = 0; cmdIdx < m_commandQueue.Count(); ++cmdIdx)
	{
		CommandQueueEntry &cmd = m_commandQueue[cmdIdx];

		switch(cmd.m_command)
		{
			case ruRENDERERCMD_RTCOPY:
				{
					g_ruEngine->CopyTexture(cmd.RTCOPY.m_srcTexture, cmd.RTCOPY.m_dstTexture, 0);
				}

				break;

			case ruRENDERERCMD_TEXCOPY:
				{
					g_ruEngine->CopyTextureWithConversion(cmd.RTCOPY.m_srcTexture, cmd.RTCOPY.m_dstTexture, 0);
				}

				break;

			case ruRENDERERCMD_FLUSH:
				{
					if(cmd.FLUSH.m_flushCount > 0)
					{
						ExecuteRenderQueue(cmd.FLUSH.m_flushIdx, cmd.FLUSH.m_flushCount, TRUE);
					}
				}

				break;

			case ruRENDERERCMD_EXECUTE_NOSORT:
				{
					if(cmd.FLUSH.m_flushCount > 0)
					{
						ExecuteRenderQueue(cmd.FLUSH.m_flushIdx, cmd.FLUSH.m_flushCount, FALSE);
					}
				}

				break;

			case ruRENDERERCMD_EXECUTE_SINGLE:
				{
					ExecuteRenderQueue(cmd.EXECUTESINGLE.m_executeIdx, 1, FALSE);
				}

				break;

			case ruRENDERERCMD_SCISSOR_CLEAR:
				{
					g_ruEngine->SetScissorRect(NULL);
				}

				break;

			case ruRENDERERCMD_SCISSOR_SET:
				{
					RECT rect;

					rect.left = cmd.SCISSOR.m_left;
					rect.right = cmd.SCISSOR.m_right;
					rect.top = cmd.SCISSOR.m_top;
					rect.bottom = cmd.SCISSOR.m_bottom;

					g_ruEngine->SetScissorRect(&rect);
				}

				break;

			case ruRENDERERCMD_VIEWPORT_SET:
				{
					if(cmd.VIEWPORTSET.m_viewport)
					{
						cmd.VIEWPORTSET.m_viewport->SelectAsViewport();
					}
					else
					{
						g_ruEngine->ResetViewport();
					}
				}

				break;

			case ruRENDERERCMD_VIEWPORT_CLEAR:
				{
					g_ruEngine->Clear(cmd.VIEWPORTCLEAR.m_clearFlags, cmd.VIEWPORTCLEAR.m_clearColor, cmd.VIEWPORTCLEAR.m_clearZ, cmd.VIEWPORTCLEAR.m_clearStencil);
				}

				break;

			case ruRENDERERCMD_RENDERTARGET_SET:
				{
					this->SelectRenderTarget(cmd.RENDERTARGETSET.m_renderTarget);
				}
		}
	}

	// Clear the command queue
	m_commandQueue.Clear();
}

void CRuSceneRenderer::ExecuteRenderQueue(INT32 flushIdx, INT32 flushCount, BOOL sortRenderQueue)
{
/*
	if(m_debug_NullRendering)
	{
		Scene_Flush_NULL();
		return;
	}
*/
	DWORD param;

	// Error check
	if(!g_ruInstanceManager)
		return;

	// Begin time sampling for the flush
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Flush, 0);

	// Sort the rendering queue (Sorting is done on the first pass only)
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Sorting, 0);

	if(sortRenderQueue)
	{
		Scene_SortRenderQueue(flushIdx, flushCount);
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_Sorting, 0);

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

	for(INT32 i = 0, numRQEntries = flushCount; i < numRQEntries; ++i)
	{
		RenderQueueEntry &curEntry = m_renderQueue[sortRenderQueue ? sortedIndices[i] + flushIdx : i + flushIdx];

		// Check visibility against anti-portal
		for(INT32 i = 0; i < m_antiPortals.Count(); ++i)
		{
			if(curEntry.m_entity && m_antiPortals[i]->IsOccluded(curEntry.m_entity->GetWorldBounds()))
			{
				// Increment probe
				RUPROFILE_SAMPLE_ACCUMULATE(m_probe_Misc_AntiPortalDiscarded, 0, 1);

				// Release mesh and material references
				ruSAFE_RELEASE(curEntry.m_mesh);
				ruSAFE_RELEASE(curEntry.m_material);

				continue;
			}
		}

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

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

			// Verify render target is valid
			if(m_renderTargets[m_selectedRTIndex].m_renderTarget)
			{
				// Select the render target
				this->SelectRenderTarget(m_renderTargets[m_selectedRTIndex].m_renderTarget);

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

		// If the render entry has a viewport specified, set it now
		if(m_selectedViewportIndex != curEntry.m_viewport)
		{
			// Set current viewport index
			m_selectedViewportIndex = curEntry.m_viewport;

			ViewportDescriptor &vpDescriptor = m_viewports[m_selectedViewportIndex];

			// Select viewport if it is not a NULL viewport
			if(vpDescriptor.m_viewport)
			{
				vpDescriptor.m_viewport->SelectAsViewport();
			}
			else
			{
				g_ruEngine->ResetViewport();
			}

			// Clear the viewport if necessary
			if(vpDescriptor.m_cleared == FALSE && vpDescriptor.m_clearFlags)
			{
				vpDescriptor.m_cleared = TRUE;
				g_ruEngine->Clear(vpDescriptor.m_clearFlags, vpDescriptor.m_clearColor, vpDescriptor.m_clearZ, vpDescriptor.m_clearStencil);
			}
		}
		else
		{
			// Critical error
		}

		if(curEntry.m_cpSetIndex != m_selectedCPSetIndex)
		{
			// Set the current clip plane set index
			m_selectedCPSetIndex = curEntry.m_cpSetIndex;

			if(m_selectedCPSetIndex >= 0)
			{
				ClipPlaneSet &cpSet = m_clipPlaneSets[m_selectedCPSetIndex];

				for(INT32 cpIdx = 0; cpIdx < 6; ++cpIdx)
				{
					if(cpSet.m_clipPlanes[cpIdx].IsZero() == FALSE)
					{
						g_ruEngine->SetClipPlane(cpIdx, cpSet.m_clipPlanes[cpIdx]);
						g_ruEngine->SetClipPlaneEnable(cpIdx, TRUE);
					}
					else
					{
						g_ruEngine->SetClipPlaneEnable(cpIdx, FALSE);
					}
				}
			}
			else
			{
				for(INT32 cpIdx = 0; cpIdx < 6; ++cpIdx)
				{
					g_ruEngine->SetClipPlaneEnable(cpIdx, FALSE);
				}
			}
		}

		// 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;

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

			RUPROFILE_SAMPLE_BEGIN(m_probe_3D_ParameterSetup, 2);

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

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

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

			RUPROFILE_SAMPLE_END(m_probe_3D_ParameterSetup, 2);

			RUPROFILE_SAMPLE_BEGIN(m_probe_3D_RenderAllPasses, 4);

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

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

					if(texture)
					{
						IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(texture);
						instancedTexture->Select(curSampler);
//						texture->Select(curSampler);
					}
					else
					{
						if(texChannel == ruTEXCHANNEL_SYSTEM_CLAMP)
						{
							IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(m_clampTexture);
							instancedTexture->Select(curSampler);
//							m_clampTexture->Select(curSampler);
						}
						else
						{
							// Force NULL texture
							IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(m_nullTexture);
							instancedTexture->Select(curSampler);
//							m_nullTexture->Select(curSampler);
						}
					}
				}

				RUPROFILE_SAMPLE_END(m_probe_3D_TextureSetup, 5);

				if(curEntry.m_paramCache->GetParameter(ruPARAMTYPE_TWEEN_FACTOR, param))
				{
					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Morphing, 5);

					REAL tweenFactor = RuCastDWORDToFloat(param);
					curEntry.m_mesh->SetMorphCurveTime(tweenFactor);

					RUPROFILE_SAMPLE_END(m_probe_3D_Morphing, 5);
				}

				// End time sampling for the instancing
				RUPROFILE_SAMPLE_BEGIN(m_probe_Engine_InstancingTime, 5);
/*
				// Instance the mesh
				IRuInstancedMesh *instancedMesh = NULL;

				// End time sampling for the instancing
				RUPROFILE_SAMPLE_END(m_probe_Engine_InstancingTime, 5);

				// 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;

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

					// Render
					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Render, 5);

					shader->BeginPass(curPass);

					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_RenderD3D, 5);

					if(m_enableRendering)
					{
						m_numPrimitivesRendered += instancedMesh->Render(batchOffset, batchNumPrimitives, 0, -1);
					}

					RUPROFILE_SAMPLE_END(m_probe_3D_RenderD3D, 5);

					++m_numBatchesIssued;
					shader->EndPass(curPass);

					RUPROFILE_SAMPLE_END(m_probe_3D_Render, 5);
				}
*/
			}

			RUPROFILE_SAMPLE_END(m_probe_3D_RenderAllPasses, 4);
		}


		// debugging
//		assert(curEntry.m_mesh->GetRefCount() > 1);
//		assert(curEntry.m_material->GetRefCount() > 1);

		ruSAFE_RELEASE(curEntry.m_mesh);
		ruSAFE_RELEASE(curEntry.m_material);

	}

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

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

	// End time sampling for the flush
	RUPROFILE_SAMPLE_END(m_probe_3D_Flush, 0);
}

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

BOOL CRuSceneRenderer::Ru2D_SetViewportScale(REAL width, REAL height)
{
	return Ru2D_PushViewportScale(width, height);
}

BOOL CRuSceneRenderer::Ru2D_PushViewportScale(REAL width, REAL height)
{
	// Push current 2D viewport size onto stack
	m_ru2D_Viewport_Width_Stack.Push(m_ru2D_Viewport_Width);
	m_ru2D_Viewport_Height_Stack.Push(m_ru2D_Viewport_Height);

	// Set new 2D viewport size
	m_ru2D_Viewport_Width = width;
	m_ru2D_Viewport_Height = height;

	return TRUE;
}

BOOL CRuSceneRenderer::Ru2D_PopViewportScale()
{
	if(m_ru2D_Viewport_Width_Stack.IsEmpty() == FALSE && m_ru2D_Viewport_Height_Stack.IsEmpty() == FALSE)
	{
		// Pop previous 2D viewport size off stack
		m_ru2D_Viewport_Width = m_ru2D_Viewport_Width_Stack.Pop();
		m_ru2D_Viewport_Height = m_ru2D_Viewport_Height_Stack.Pop();

		return TRUE;
	}

	return FALSE;
}

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

BOOL CRuSceneRenderer::Ru2D_SetScissorRect(CRuRectangle *scissorRect)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Force flush of all previously queued 2D batches
	Ru2D_Flush();

	if(scissorRect)
	{
		CRuRectangle temp(scissorRect->m_left, scissorRect->m_top, scissorRect->m_right, scissorRect->m_bottom);

		// Convert scissor coordinates...
		Ru2D_ConvertCoordinates(temp);

		CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

		command.m_command = ruRENDERERCMD_SCISSOR_SET;

		command.SCISSOR.m_left = (INT32) temp.m_left;
		command.SCISSOR.m_right = (INT32) temp.m_right;
		command.SCISSOR.m_top = (INT32) temp.m_top;
		command.SCISSOR.m_bottom = (INT32) temp.m_bottom;
	}
	else
	{
		CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

		command.m_command = ruRENDERERCMD_SCISSOR_CLEAR;
	}

	return TRUE;
}

/*!
	Sets the current uv mapping mode for the 2D toolkit. This takes effect
	immediately, does not flush the rendering pipeline, and will only affect
	new 2D submissions.

	@retval TRUE The operation succeeded.
	@retval FALSE The operation failed.
*/
BOOL CRuSceneRenderer::Ru2D_SetUVMode(Ru2D_UVMode uvMode)
{
	m_ru2D_UVMode = uvMode;

	return TRUE;
}

/*!
	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)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// 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;
				m_ru2D_StencilID = 1;
			}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			break;
	}

	// If the shader was properly selected, return TRUE
	if(shader)
	{
		m_ru2D_Material->SetShader(shader);
		m_ru2D_RefMaterial->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)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	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_STENCILID:
			{
				m_ru2D_StencilID = static_cast<UINT8>(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)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	m_ru2D_Material->GetParameterBlock()->SetParameter(paramType, param);

	return TRUE;
}

/*!
	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, BOOL allowBinding, RuTextureChannel texChannel)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	IRuTexture *boundTexture = NULL;

	if(texture)
	{
		if(allowBinding)
		{
			// Store active texture handle
			m_ru2D_ActiveTextureHandle = m_ru2D_TextureAtlas->BindIntoAtlas(texture, FALSE);
			m_ru2D_ActiveTextureWidth = m_ru2D_TextureAtlas->GetTextureWidth(m_ru2D_ActiveTextureHandle);
			m_ru2D_ActiveTextureHeight = m_ru2D_TextureAtlas->GetTextureHeight(m_ru2D_ActiveTextureHandle);
			boundTexture = m_ru2D_TextureAtlas->GetTexture(m_ru2D_ActiveTextureHandle);
		}
		else
		{
			m_ru2D_ActiveTextureHandle = -1;
			boundTexture = texture;
		}
	}

	// 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);
	m_ru2D_RefMaterial->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;
}

BOOL CRuSceneRenderer::Ru2D_SetRotation(REAL rotation)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Flush current batch if necessary
	if(-rotation != m_ru2D_Rotation)
	{
		Ru2D_RenderBatch();
	}

	// Store new rotation
	m_ru2D_Rotation = -rotation;

	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);
}

/*!
	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)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Accumulate batch count
//	g_ruProfiler->AccumulateProbeValue(m_ru2D_PrimitiveCount, static_cast<REAL>(1));

	REAL baseGeometryOffset = 0.5f;
	REAL baseTexelOffset = 0.75f;

	switch(m_ru2D_UVMode)
	{
		case ru2DUVMODE_OFFSET:
			baseGeometryOffset = 0.5f;
			baseTexelOffset = 1.0f;
			break;
		case ru2DUVMODE_DIRECT:
			baseGeometryOffset = 0.0f;
			baseTexelOffset = 0.0f;
			break;
	}

	// 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(dst);

		// Calculate destination vertices

		REAL pixelOffsetX = baseGeometryOffset / m_ru2D_RefRTWidth;
		REAL pixelOffsetY = baseGeometryOffset / 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)
		{
			// Begin time sampling for the flush
			RUPROFILE_SAMPLE_BEGIN(m_probe_2D_Submit, 2);

			// End time sampling for the flush
			RUPROFILE_SAMPLE_END(m_probe_2D_Submit, 2);

			// 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) m_ru2D_ActiveTextureWidth, src.m_top / (REAL) m_ru2D_ActiveTextureHeight, 0.0f, 0.0f, baseTexelOffset, u0, v0);
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_right / (REAL) m_ru2D_ActiveTextureWidth, src.m_bottom / (REAL) m_ru2D_ActiveTextureHeight, 0.0f, 0.0f, baseTexelOffset, u1, v1);
					break;

				case ru2DFXTYPE_GAUSSIAN:
				case ru2DFXTYPE_BLIT_FILTER:
				case ru2DFXTYPE_BLIT_ADD:
				case ru2DFXTYPE_BLIT_MASK:
				case ru2DFXTYPE_BLIT_MASKED:
				case ru2DFXTYPE_BLIT_MASK_Z:
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_left / (REAL) m_ru2D_ActiveTextureWidth, src.m_top / (REAL) m_ru2D_ActiveTextureHeight, 1.0f, 1.0f, baseTexelOffset, u0, v0);
					m_ru2D_TextureAtlas->TranslateUV(m_ru2D_ActiveTextureHandle, src.m_right / (REAL) m_ru2D_ActiveTextureWidth, src.m_bottom / (REAL) m_ru2D_ActiveTextureHeight, -1.0f, -1.0f, baseTexelOffset, 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:
				case ru2DFXTYPE_BLIT_ADD:
					{
						REAL texelOffsetX = baseTexelOffset / m_ru2D_RefTextureWidth;
						REAL texelOffsetY = baseTexelOffset / m_ru2D_RefTextureHeight;

						u0 = src.m_left / (REAL) m_ru2D_RefTextureWidth + 0.0f;
						u1 = src.m_right / (REAL) m_ru2D_RefTextureWidth - 0.0f;
						v0 = src.m_top / (REAL) m_ru2D_RefTextureHeight + 0.0f;
						v1 = src.m_bottom / (REAL) m_ru2D_RefTextureHeight - 0.0f;
					}

					break;
			}

		}

		INT32 constantOffset = m_ru2D_PrimitiveCount * 12;
		DWORD vertexColor = m_ru2D_DiffuseColor;

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

		m_ru2D_MaterialConstants[constantOffset++] = x0;
		m_ru2D_MaterialConstants[constantOffset++] = u0;
		m_ru2D_MaterialConstants[constantOffset++] = y0;
		m_ru2D_MaterialConstants[constantOffset++] = v0;

		m_ru2D_MaterialConstants[constantOffset++] = x1;
		m_ru2D_MaterialConstants[constantOffset++] = u1;
		m_ru2D_MaterialConstants[constantOffset++] = y1;
		m_ru2D_MaterialConstants[constantOffset++] = v1;

		m_ru2D_MaterialConstants[constantOffset++] = static_cast<REAL>((vertexColor >> 16) & 0x000000FF) / 255.0f;
		m_ru2D_MaterialConstants[constantOffset++] = static_cast<REAL>((vertexColor >>  8) & 0x000000FF) / 255.0f;
		m_ru2D_MaterialConstants[constantOffset++] = static_cast<REAL>((vertexColor >>  0) & 0x000000FF) / 255.0f;
		m_ru2D_MaterialConstants[constantOffset++] = static_cast<REAL>((vertexColor >> 24) & 0x000000FF) / 255.0f;

		// Update primitive information
		++m_ru2D_PrimitiveCount;
	}

	// End time sampling for the flush
//	RUPROFILE_SAMPLE_END(m_probe_2D_Submit, 2);
}

void CRuSceneRenderer::Ru2D_Flush()
{
	// Flush the last batch to render queue
	Ru2D_RenderBatch();

	if(m_scene_FlushIdx < m_renderQueue.Count())
	{
		// Queue command to execute all 2D commands
		CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

		command.m_command = ruRENDERERCMD_EXECUTE_NOSORT;
		command.FLUSH.m_flushIdx = m_scene_FlushIdx;
		command.FLUSH.m_flushCount = m_renderQueue.Count() - m_scene_FlushIdx;

		m_scene_FlushIdx = m_renderQueue.Count();
	}
}

void CRuSceneRenderer::Ru2D_ConvertCoordinates(CRuVector3 &point)
{
	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;

//		point.m_x = floor(point.m_x * xFactor);
//		point.m_y = floor(point.m_y * yFactor);

		point.m_x = (point.m_x * xFactor);
		point.m_y = (point.m_y * yFactor);
	}
}

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);

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

/*!
	Converts a point from world coordinate from to viewport coordinate frame

	@param camera Source camera to use for conversion.
	@param point Point to convert.
*/
void CRuSceneRenderer::Ru2D_WorldToViewport(CRuCamera *camera, CRuVector3 &point)
{
	// Transform point into homogeneous coordinates
	const CRuMatrix4x4 &frustumTransform = camera->GetFrustumTransform();
	frustumTransform.TransformPointProjected(point);

	// Convert coordinates into viewport scale
	if(m_ru2D_Viewport_Width > 0.0f && m_ru2D_Viewport_Height > 0.0f)
	{
		point.m_x = ((point.m_x + 1.0f) / 2.0f) * m_ru2D_Viewport_Width;
		point.m_y = ((-point.m_y + 1.0f) / 2.0f) * m_ru2D_Viewport_Height;
		point.m_z = 0.0f;
	}
	else
	{
		point.m_x = ((point.m_x + 1.0f) / 2.0f) * m_ru2D_RefRTWidth;
		point.m_y = ((-point.m_y + 1.0f) / 2.0f) * m_ru2D_RefRTHeight;
		point.m_z = 0.0f;
	}
}

void CRuSceneRenderer::Ru2D_WorldToScreen(CRuCamera *camera, CRuVector3 &point)
{
	// Transform point into homogeneous coordinates
	const CRuMatrix4x4 &frustumTransform = camera->GetFrustumTransform();
	frustumTransform.TransformPointProjected(point);

	// Convert coordinates into screen scale
	point.m_x = ((point.m_x + 1.0f) / 2.0f) * m_ru2D_RefRTWidth;
	point.m_y = ((-point.m_y + 1.0f) / 2.0f) * m_ru2D_RefRTHeight;
	point.m_z = 0.0f;
}

void CRuSceneRenderer::Ru2D_HomogeneousToViewport(CRuVector3 &point)
{
	REAL pixelOffsetX = 0.5f / m_ru2D_RefRTWidth;
	REAL pixelOffsetY = 0.5f / m_ru2D_RefRTHeight;

	point.m_x = ((point.m_x + pixelOffsetX) / 2.0f + 0.5f) * m_ru2D_RefRTWidth;
	point.m_y = ((-point.m_y + pixelOffsetY) / 2.0f + 0.5f) * m_ru2D_RefRTHeight;
}

void CRuSceneRenderer::Ru2D_HomogeneousToViewport(CRuRectangle &rectangle)
{
	REAL pixelOffsetX = 0.5f / m_ru2D_RefRTWidth;
	REAL pixelOffsetY = 0.5f / m_ru2D_RefRTHeight;

	rectangle.m_left = ((rectangle.m_left + pixelOffsetX) / 2.0f + 0.5f) * m_ru2D_RefRTWidth;
	rectangle.m_right = ((rectangle.m_right + pixelOffsetX) / 2.0f + 0.5f) * m_ru2D_RefRTWidth;
	rectangle.m_top = ((-rectangle.m_top + pixelOffsetY) / 2.0f + 0.5f) * m_ru2D_RefRTHeight;
	rectangle.m_bottom = ((-rectangle.m_bottom + pixelOffsetY) / 2.0f + 0.5f) * m_ru2D_RefRTHeight;
}

void CRuSceneRenderer::Ru2D_ViewportToHomogeneous(CRuVector3 &point)
{
	REAL pixelOffsetX = 0.5f / m_ru2D_RefRTWidth;
	REAL pixelOffsetY = 0.5f / m_ru2D_RefRTHeight;

	point.m_x = ((point.m_x / m_ru2D_RefRTWidth) - 0.5f) * 2.0f - pixelOffsetX;
	point.m_y = -(((point.m_y / m_ru2D_RefRTHeight) - 0.5f) * 2.0f - pixelOffsetY);
}

void CRuSceneRenderer::Ru2D_ViewportToHomogeneous(CRuRectangle &rectangle)
{
	REAL pixelOffsetX = 0.5f / m_ru2D_RefRTWidth;
	REAL pixelOffsetY = 0.5f / m_ru2D_RefRTHeight;

	rectangle.m_left = ((rectangle.m_left / m_ru2D_RefRTWidth) - 0.5f) * 2.0f - pixelOffsetX;
	rectangle.m_right = ((rectangle.m_right / m_ru2D_RefRTWidth) - 0.5f) * 2.0f - pixelOffsetX;
	rectangle.m_top = -(((rectangle.m_top / m_ru2D_RefRTHeight) - 0.5f) * 2.0f - pixelOffsetY);
	rectangle.m_bottom = -(((rectangle.m_bottom / m_ru2D_RefRTHeight) - 0.5f) * 2.0f - pixelOffsetY);
}

void CRuSceneRenderer::Ru3D_Submit(CRuMatrix4x4 *worldTransform, IRuMesh *mesh, IRuMaterial *material)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_3D_IMMEDIATE);

	// Submit
	Scene_Submit_Internal(NULL, worldTransform, mesh, material, m_camera, TRUE);

	// Insert execute single command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_EXECUTE_SINGLE;
	command.EXECUTESINGLE.m_executeIdx = m_renderQueue.Count() - 1;
}

void CRuSceneRenderer::RuX_CopyTexture(IRuEngineResource *srcTexture, IRuEngineResource *dstTexture)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Insert copy texture command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_RTCOPY;
	command.RTCOPY.m_srcTexture = srcTexture;
	command.RTCOPY.m_dstTexture = dstTexture;
}

void CRuSceneRenderer::RuX_ConvertTexture(IRuEngineResource *srcTexture, IRuEngineResource *dstTexture)
{
	// Transition renderer state
	TransitionRendererState(ruRENDERERSTATE_2D);

	// Insert copy texture command
	CommandQueueEntry &command = Scene_AllocateCommandQueueEntry();

	command.m_command = ruRENDERERCMD_TEXCOPY;
	command.RTCOPY.m_srcTexture = srcTexture;
	command.RTCOPY.m_dstTexture = dstTexture;
}

const CRuProfiler *CRuSceneRenderer::GetSceneProfiler() const
{
	return g_ruProfiler;
}

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;
}

void CRuSceneRenderer::FlushMatrixStack()
{
	while(m_usedMatrixStack.IsEmpty() == FALSE)
	{
		m_unusedMatrixStack.Push(m_usedMatrixStack.Pop());
	}
}

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);

	// Clear the parameter cache
	paramCache->ClearParameterCache();

	return paramCache;
}

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

CRuMaterial *CRuSceneRenderer::Ru2D_AllocateMaterial()
{
	CRuMaterial *material = NULL;

	// Get a material object
	if(m_unusedMaterialStack.IsEmpty())
	{
		material = ruNEW CRuMaterial();

		REAL *instanceData = ruNEW REAL [256 * 4];
		material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GEOINSTANCE_S_DATA, reinterpret_cast<DWORD>(instanceData));
	}
	else
	{
		material = m_unusedMaterialStack.Pop();
	}

	// Push material object onto used stack
	m_usedMaterialStack.Push(material);

	// Copy textures from the reference material
	for(INT32 i = 0; i < m_ru2D_RefMaterial->GetNumTextureChannels(); ++i)
	{
		RuTextureChannel texChannel = m_ru2D_RefMaterial->GetTextureChannel(i);
		IRuBaseTexture *texture = m_ru2D_RefMaterial->GetTexture(texChannel);

		material->SetTexture(texChannel, texture);
	}

	// Copy shader from the reference material
	material->SetShader(m_ru2D_RefMaterial->GetShader());

	// Set material pointer
	m_ru2D_Material = material;

	// Set instance data pointer
	DWORD instanceDataParam;
	m_ru2D_Material->GetParameterBlock()->GetParameter(ruPARAMTYPE_GEOINSTANCE_S_DATA, instanceDataParam);
	m_ru2D_MaterialConstants = reinterpret_cast<REAL *>(instanceDataParam);

	return material;
}

void CRuSceneRenderer::Ru2D_FlushMaterialStack()
{
	while(m_usedMaterialStack.IsEmpty() == FALSE)
	{
		m_unusedMaterialStack.Push(m_usedMaterialStack.Pop());
	}

	m_ru2D_Material = NULL;
	m_ru2D_MaterialConstants = NULL;
}

void CRuSceneRenderer::GarbageCollectSkinningCache()
{
	m_gcSkinningCache.Clear();

	m_skinningCache->ForAllElements(boost::bind(&CRuSceneRenderer::GarbageCollectSkinningCacheCallback, this, _1, _2));

	for(INT32 i = 0; i < m_gcSkinningCache.Count(); ++i)
	{
		CachedSkin cachedSkin;
		if(m_skinningCache->Get(m_gcSkinningCache[i], m_gcSkinningCache[i].HashCode(), cachedSkin))
		{
/*
			char outputStr[2048];
			sprintf(outputStr, "GC skinning hash entry\n");
			OutputDebugString(outputStr);
*/
			// Release the skinnable mesh
			ruSAFE_RELEASE(cachedSkin.m_mesh);

			// Release any subdivided meshes
			if(cachedSkin.m_subdividedMeshes)
			{
				for(INT32 i = 0; i < cachedSkin.m_subdividedMeshes->Count(); ++i)
				{
					ruSAFE_RELEASE(cachedSkin.m_subdividedMeshes->Get(i));
				}

				delete cachedSkin.m_subdividedMeshes;
			}

			// Remove cache entry
			m_skinningCache->Remove(m_gcSkinningCache[i], m_gcSkinningCache[i].HashCode());
		}
	}
}

BOOL CRuSceneRenderer::GarbageCollectSkinningCacheCallback(const void *key, void *data)
{
	CachedSkin &cachedSkin = *reinterpret_cast<CachedSkin *>(data);

	DWORD t = timeGetTime();
	DWORD et = t - cachedSkin.m_lastUpdateTime;

	if(et > 15000)
	{
		m_gcSkinningCache.Add(*reinterpret_cast<const CRuGUID *>(key));
	}

	return TRUE;
}

void CRuSceneRenderer::FlushSkinningCache()
{
	m_skinningCache->ForAllElements(boost::bind(&CRuSceneRenderer::FlushSkinningCacheCallback, this, _1, _2));
	m_skinningCache->Clear();
}

BOOL CRuSceneRenderer::FlushSkinningCacheCallback(const void *key, void *data)
{
	CachedSkin &cachedSkin = *reinterpret_cast<CachedSkin *>(data);

	// Release the skinnable mesh
	ruSAFE_RELEASE(cachedSkin.m_mesh);

	// Release any subdivided meshes
	if(cachedSkin.m_subdividedMeshes)
	{
		for(INT32 i = 0; i < cachedSkin.m_subdividedMeshes->Count(); ++i)
		{
			ruSAFE_RELEASE(cachedSkin.m_subdividedMeshes->Get(i));
		}

		delete cachedSkin.m_subdividedMeshes;
	}

	return TRUE;
}

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)
	{
		// Set default ambient level
		lightInfo->m_ambientLight.Set(1.0f, 0.1f, 0.1f, 0.1f);

		// Reset lighting coefficients
		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;
		invWT.Rotate(renderableEntity->GetRotation(ruFRAMETYPE_WORLD));
		invWT.Translate(renderableEntity->GetTranslation(ruFRAMETYPE_WORLD));
		invWT = invWT.GetInverse();

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

		// Transform specular position from world space into object space
		CRuVector3 localSpecularDirection;
		invWT.TransformVector(m_specularDirection, localSpecularDirection);
		lightInfo->m_specularDirection = localSpecularDirection;
//		lightInfo->m_specularDirection = 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 RuLightSettings &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 RuLightSettings &light = static_cast<CRuEntity_Light *>(m_enumeratedLights[sortedIndices[i]])->GetLight();

			switch(light.m_type)
			{
				case ruLIGHTTYPE_AMBIENT:
					{
						lightInfo->m_ambientLight.Set(light.m_diffuse.GetARGB());
					}

					break;

				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_SortRenderQueue(INT32 flushIdx, INT32 flushCount)
{
	if(flushIdx < 0)
	{
		flushIdx = 0;
		flushCount = m_renderQueue.Count();
	}

	for(INT32 i = 0; i < 9; ++i)
	{
		m_sortCriterias[i].SetNumEntries(flushCount);
	}

	for(INT32 i = flushIdx, j = 0; j < flushCount; ++i, ++j)
	{
		RenderQueueEntry &curEntry = m_renderQueue[i];

		IRuShader *shader = curEntry.m_shader;

		// Use render target priority as the first sorting criteria
		m_sortCriterias[0][j] = m_renderTargets[curEntry.m_rtIndex].m_priority;

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

		// Use shader class as the third sorting criteria
		m_sortCriterias[2][j] = shader->GetShaderClass();

		// Use entity priority as the sixth sorting criteria
		m_sortCriterias[3][j] = RuCastFloatToDWORD(curEntry.m_priority);

		// Use shader priority as the third sorting criteria
		m_sortCriterias[4][j] = shader->GetShaderPriority();

		// Use shader address as the fourth sorting criteria
		m_sortCriterias[5][j] = reinterpret_cast<UINT32>(shader);

		// Use material priority as the fifth sorting criteria
		m_sortCriterias[6][j] = curEntry.m_material->GetMaterialPriority();

		// 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 = curEntry.m_material->GetTexture(samplerChannel);
			}

			m_sortCriterias[curSampler + 7][j] = reinterpret_cast<UINT32>(samplerTexture);
		}
	}

	// Perform radix sort backwards
	for(INT32 i = 8; i >= 0; --i)
	{
		if(i == 3)
			m_radixSort.Sort((REAL *) m_sortCriterias[i].GetArray(), flushCount, (i == 8) ? FALSE : TRUE);
		else
			m_radixSort.Sort((UINT32 *) m_sortCriterias[i].GetArray(), flushCount, FALSE, (i == 8) ? FALSE : TRUE);
	}
}

void CRuSceneRenderer::Ru2D_RenderBatch()
{
	if(m_ru2D_PrimitiveCount > 0)
	{
		// Accumulate batch size
		g_ruProfiler->AccumulateProbeValue(m_probe_2D_BatchSize, static_cast<REAL>(m_ru2D_PrimitiveCount));

		// Accumulate batch count
		g_ruProfiler->AccumulateProbeValue(m_probe_2D_BatchCount, 1.0f);

		// Begin time sampling for the flush
//		RUPROFILE_SAMPLE_BEGIN(m_probe_2D_Flush, 0);

		CRuParameterBlock *paramBlock = m_ru2D_Material->GetParameterBlock();

		IRuBaseTexture *texture = m_ru2D_Material->GetTexture(ruTEXCHANNEL_DIFFUSEMAP);
		if(texture)
		{
			paramBlock->SetParameter(ruPARAMTYPE_2DFX_TEXTURE_WIDTH, static_cast<IRuTexture *>(texture)->GetLevelWidth(0));
			paramBlock->SetParameter(ruPARAMTYPE_2DFX_TEXTURE_HEIGHT, static_cast<IRuTexture *>(texture)->GetLevelHeight(0));
		}
		else
		{
			paramBlock->SetParameter(ruPARAMTYPE_2DFX_TEXTURE_WIDTH, 512);
			paramBlock->SetParameter(ruPARAMTYPE_2DFX_TEXTURE_HEIGHT, 512);
		}

		// Set rendering parameters
		REAL aspectRatio_XY = (REAL) m_ru2D_RefRTWidth / m_ru2D_RefRTHeight;

		paramBlock->SetParameter(ruPARAMTYPE_GEOINSTANCE_S_COUNT, m_ru2D_PrimitiveCount);
		paramBlock->SetParameter(ruPARAMTYPE_CUSTOM_0, RuCastFloatToDWORD(m_ru2D_Rotation));
		paramBlock->SetParameter(ruPARAMTYPE_CUSTOM_1, RuCastFloatToDWORD(aspectRatio_XY));
		paramBlock->SetParameter(ruPARAMTYPE_CUSTOM_2, RuCastFloatToDWORD(sin(m_ru2D_Rotation)));
		paramBlock->SetParameter(ruPARAMTYPE_CUSTOM_3, RuCastFloatToDWORD(cos(m_ru2D_Rotation)));

		paramBlock->SetParameter(ruPARAMTYPE_SYSTEM_STENCILID, m_ru2D_StencilID);

		// Submit
		Scene_Submit_Internal(NULL, NULL, m_ru2D_ProxyMeshes[m_ru2D_PrimitiveCount], m_ru2D_Material, m_camera, TRUE);

		// Allocate new material for use by the 2D system
		Ru2D_AllocateMaterial();

		// Reset primitive count
		m_ru2D_PrimitiveCount = 0;

		// End time sampling for the flush
//		RUPROFILE_SAMPLE_END(m_probe_2D_Flush, 0);
	}
}

BOOL CRuSceneRenderer::IsEventSuppressed(RuEntityEvent entityEvent)
{
	for(INT32 i = 0; i < m_eventSuppressionList.Count(); ++i)
	{
		if(m_eventSuppressionList[i] == entityEvent)
		{
			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuSceneRenderer::TransitionRendererState_Backend(CRuSceneRenderer::RuRendererState newRendererState)
{
	// Flush any existing workload
	switch(m_rendererState)
	{
		case ruRENDERERSTATE_STOPPED:
			{
				// Post an error - renderer must be in an operating state
				return FALSE;
			}

			break;

		case ruRENDERERSTATE_2D:
			{
				// Flush all 2D workload
				Ru2D_Flush();
			}

			break;

		case ruRENDERERSTATE_3D:
			{
				// Flush all 3D workload
				Scene_Flush();
			}

			break;

		case ruRENDERERSTATE_3D_IMMEDIATE:
			{
			}

			break;
	}

	// Set new renderer state
	m_rendererState = newRendererState;

	// Perform any transitioning state changes
	switch(m_rendererState)
	{
		case ruRENDERERSTATE_2D:
			{
				// Refresh pointers to each component
				m_ru2D_Mesh_Position = m_ru2D_Mesh->GetMorphTarget(0)->GetPosition();
				m_ru2D_Mesh_Diffuse = m_ru2D_Mesh->GetMorphTarget(0)->GetDiffuse();
				m_ru2D_Mesh_UV0 = 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 UV mode
				m_ru2D_UVMode = ru2DUVMODE_OFFSET;

				// Reset effect type
				m_ru2D_EffectType = ru2DFXTYPE_FLAT;

				m_ru2D_Rotation = 0.0f;

				// Allocate new material for use by the 2D system
				if(m_ru2D_Material == NULL)
				{
					Ru2D_AllocateMaterial();
				}

				// TEMP: this clears anti portals to get ready for next phase of 3D render, if available... but this is actually just a band-aid fix
				m_antiPortals.Clear();
			}

			break;

		case ruRENDERERSTATE_3D:
			{
			}

			break;

		case ruRENDERERSTATE_3D_IMMEDIATE:
			{
			}

			break;
	}

	// Reset viewport
	g_ruEngine->ResetViewport();

	return TRUE;
}

BOOL CRuSceneRenderer::SelectRenderTarget(IRuObject *renderTarget)
{
	// Select the render target
	if(renderTarget->GetType().IsSubClassOf(IRuSurface::Type))
	{
		static_cast<IRuSurface *>(renderTarget)->SelectAsRenderTarget(0);
	}
	else if(renderTarget->GetType().IsSubClassOf(IRuSwapChain::Type))
	{
		static_cast<IRuSwapChain *>(renderTarget)->SelectAsRenderTarget(0);
	}
	else if(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 *>(renderTarget)->SelectAsRenderTarget(0, 0);
	}
	else
	{
		// Log an error
		return FALSE;
	}

	// Update reference render target size
	g_ruEngine->GetRenderTargetSize(m_ru2D_RefRTWidth, m_ru2D_RefRTHeight);

	return TRUE;
}

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

CRuRendererOptions				g_ruRendererOptions;
IRuSceneRenderer*				g_ruSceneRenderer = NULL;

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

#pragma managed
