#include "AdeptHeaders.hpp"
#include "LightManager.hpp"
#include "CollisionVolume.hpp"
#include <MLR\MLRAmbientLight.hpp>
#include <MLR\MLRInfiniteLight.hpp>
#include <MLR\MLRSpotLight.hpp>
#include <MLR\MLRPointLight.hpp>
#include <MLR\MLRProjectLight.hpp>
#include <MLR\MLRShadowLight.hpp>
#include <MLR\MLRCenterPointLight.hpp>
#include "CollisionGrid.hpp"
#include "Tile.hpp"
#include "Map.hpp"
#include "Zone.hpp"
#include <MLR\MLRLightMap.hpp>
#include <MLR\MLRLookUpLight.hpp>
#include <ElementRenderer\ShapeElement.hpp>
#include <ElementRenderer\CameraElement.hpp>
#include <ElementRenderer\SwitchElement.hpp>
#include <ElementRenderer\ShapeLODElement.hpp>
#include <ElementRenderer\LODElement.hpp>
#include "VideoRenderer.hpp"
#include <MLR\MLRTexturePool.hpp>
#include <MLR\MLRClipper.hpp>
#include <MLR\MLRTexture.hpp>

static DWORD Light_Count=0;
extern void _stdcall End3DScene();
DECLARE_TIMER(static, Shadow_Casting);
DECLARE_TIMER(static, Shadow_Start);
DECLARE_TIMER(static, Shadow_Traversal);
DECLARE_TIMER(static, Shadow_End);

#define HUNT_LIGHT_CRASH

#if 0
#define SHADOW_RENDER(string) RENDER("Shadow::" string)
#else
#define SHADOW_RENDER(string)
#endif

//------------------------------------------------------------------------------
//
EnvironmentalLight::EnvironmentalLight(MidLevelRenderer::MLRLight *light):
	gosFX::Light(light)
{
	Check_Pointer(this);
	Check_Object(light);
}

//------------------------------------------------------------------------------
//
EnvironmentalLight::~EnvironmentalLight()
{
	Check_Object(this);
}

//------------------------------------------------------------------------------
//
AmbientLight::AmbientLight(MidLevelRenderer::MLRAmbientLight *light):
	EnvironmentalLight(light)
{
	Check_Pointer(this);
	Check_Object(light);
}

//------------------------------------------------------------------------------
//
InfiniteLight::InfiniteLight(MidLevelRenderer::MLRInfiniteLight *light):
	EnvironmentalLight(light)
{
	Check_Pointer(this);
	Check_Object(light);
}

//------------------------------------------------------------------------------
//
LookupLight::LookupLight(MidLevelRenderer::MLRLookUpLight *light):
	InfiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);
}

//------------------------------------------------------------------------------
//
FiniteLight::FiniteLight(MidLevelRenderer::MLRInfiniteLightWithFalloff *light):
	gosFX::Light(light)
{
	Check_Pointer(this);
	Check_Object(light);
	m_minRow = m_maxRow = m_minColumn = m_maxColumn = 255;
	Set_Statistic(Light_Count, Light_Count+1);
	if (light->GetLightMask()&MidLevelRenderer::MLRState::LightMapLightingMode)
		light->SetDynamicLight();
}

//------------------------------------------------------------------------------
//
FiniteLight::~FiniteLight()
{
	Check_Object(this);

	Set_Statistic(Light_Count, Light_Count-1);
	if (CollisionGrid::Instance && m_minRow != 255)
	{
		Check_Object(CollisionGrid::Instance);
		for (BYTE z=m_minRow; z<=m_maxRow; ++z)
		{
			for (BYTE x=m_minColumn; x<=m_maxColumn; ++x)
			{
				Tile *tile = CollisionGrid::Instance->GetTile(z, x);
				Check_Object(tile);
				tile->m_lightsChanged = true;
			}
		}
	}
}

//------------------------------------------------------------------------------
//
void
	FiniteLight::ChangeLight(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	//
	//-----------------------------------------------------------------------
	// Update the radius, then compute what the new tile limits of the lights
	// are
	//-----------------------------------------------------------------------
	//
//	BaseClass::ChangeLight(info);
	Check_Object(CollisionGrid::Instance);
	Scalar row_scale = 1.0f/CollisionGrid::Instance->m_rowCellSize;
	Scalar column_scale = 1.0f/CollisionGrid::Instance->m_columnCellSize;
	Scalar x = info->m_origin(3,0);
	Scalar z = info->m_origin(3,2);

	//
	//-------------------------------
	// Figure out the grid boundaries
	//-------------------------------
	//
	Scalar min_z =
		(z - info->m_outer - CollisionGrid::Instance->m_rowOffset) * row_scale;
	BYTE min_row =
		(min_z < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_z);
	Scalar max_z =
		(z + info->m_outer - CollisionGrid::Instance->m_rowOffset) * row_scale;
	BYTE max_row = Truncate_Float_To_Byte(max_z);
	if (max_row >= CollisionGrid::Instance->m_rowCellCount)
		max_row =
			static_cast<BYTE>(CollisionGrid::Instance->m_rowCellCount - 1);

	Scalar min_x =
		(x - info->m_outer - CollisionGrid::Instance->m_columnOffset) * column_scale;
	BYTE min_col =
		(min_x < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_x);
	Scalar max_x =
		(x + info->m_outer - CollisionGrid::Instance->m_columnOffset) * column_scale;
	BYTE max_col = Truncate_Float_To_Byte(max_x);
	if (max_col >= CollisionGrid::Instance->m_columnCellCount)
		max_col =
			static_cast<BYTE>(CollisionGrid::Instance->m_columnCellCount - 1);

	//
	//------------------------------------------------
	// Update the tile boundaries if they have changed
	//------------------------------------------------
	//
	if (
		min_row != m_minRow || max_row != m_maxRow
		 || min_col != m_minColumn || max_col != m_maxColumn ||
		 (info->m_intensity<Stuff::SMALL)^(GetMLRLight()->GetIntensity()<Stuff::SMALL)
	)
	{

		//
		//------------------------------------
		// Remove the light from all the zones
		//------------------------------------
		//
		if (m_minRow < 255)
		{
			Verify(
				m_minColumn <= m_maxColumn
				 && m_maxColumn < CollisionGrid::Instance->m_columnCellCount
			);
			Verify(
				m_minRow <= m_maxRow
				 && m_maxRow < CollisionGrid::Instance->m_rowCellCount
			);
			for (BYTE z=m_minRow; z<=m_maxRow; ++z)
			{
				for (BYTE x=m_minColumn; x<=m_maxColumn; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					Check_Object(tile);
					tile->m_lights.RemovePlug(this);
					tile->m_lightsChanged = true;
				}
			}
		}

		//
		//------------------------
		// Add it to the new zones
		//------------------------
		//
		m_minRow = min_row;
		m_maxRow = max_row;
		m_minColumn = min_col;
		m_maxColumn = max_col;
		Verify(
			m_minColumn <= m_maxColumn
			 && m_maxColumn < CollisionGrid::Instance->m_columnCellCount
		);
		Verify(
			m_minRow <= m_maxRow
			 && m_maxRow < CollisionGrid::Instance->m_rowCellCount
		);
		if(info->m_intensity > Stuff::SMALL)
		{
			for (BYTE z=m_minRow; z<=m_maxRow; ++z)
			{
				for (BYTE x=m_minColumn; x<=m_maxColumn; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					Check_Object(tile);
					tile->m_lights.Add(this);
					tile->m_lightsChanged = true;
				}
			}
		}
	}

	BaseClass::ChangeLight(info);


	MidLevelRenderer::MLRInfiniteLightWithFalloff *light = GetMLRLight();
	Check_Object(light);
	light->SetFalloffDistance(info->m_inner, info->m_outer);
}

//------------------------------------------------------------------------------
//
void
	FiniteLight::GetInfo(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::GetInfo(info);
	MidLevelRenderer::MLRInfiniteLightWithFalloff *light = GetMLRLight();
	Check_Object(light);
	light->GetFalloffDistance(
		info->m_inner,
		info->m_outer
	);
}

//------------------------------------------------------------------------------
//
PointLight::PointLight(MidLevelRenderer::MLRPointLight *light):
	FiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);

	gosFX::LightManager::Info info;
	PointLight::GetInfo(&info);
	if (info.m_outer > 0.0f)
		PointLight::ChangeLight(&info);
}

//------------------------------------------------------------------------------
//
SpotLight::SpotLight(MidLevelRenderer::MLRSpotLight *light):
	FiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);

	gosFX::LightManager::Info info;
	SpotLight::GetInfo(&info);
	if (info.m_outer > 0.0f)
		SpotLight::ChangeLight(&info);
}

//------------------------------------------------------------------------------
//
void
	SpotLight::ChangeLight(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::ChangeLight(info);
	MidLevelRenderer::MLRSpotLight *light = GetMLRLight();
	Check_Object(light);
	light->SetSpreadAngle(info->m_spread);
}

//------------------------------------------------------------------------------
//
void
	SpotLight::GetInfo(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::GetInfo(info);
	MidLevelRenderer::MLRSpotLight *light = GetMLRLight();
	Check_Object(light);
	light->GetSpreadAngle(&info->m_spread);
}

//------------------------------------------------------------------------------
//
ProjectLight::ProjectLight(MidLevelRenderer::MLRProjectLight *light):
	FiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);

	gosFX::LightManager::Info info;
	ProjectLight::GetInfo(&info);
	if (info.m_outer > 0.0f)
		ProjectLight::ChangeLight(&info);
}

//------------------------------------------------------------------------------
//
void
	ProjectLight::ChangeLight(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::ChangeLight(info);
	MidLevelRenderer::MLRProjectLight *light = GetMLRLight();
	Check_Object(light);
	light->SetSpreadAngle(info->m_spread);
}

//------------------------------------------------------------------------------
//
void
	ProjectLight::GetInfo(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::GetInfo(info);
	MidLevelRenderer::MLRProjectLight *light = GetMLRLight();
	Check_Object(light);
	light->GetSpreadAngle(&info->m_spread);
}

//------------------------------------------------------------------------------
//
ShadowLight::ShadowLight(MidLevelRenderer::MLRShadowLight *light):
	FiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);
}

//------------------------------------------------------------------------------
//
void
	ShadowLight::ChangeLight(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::ChangeLight(info);
//	MidLevelRenderer::MLRShadowLight *light = GetMLRLight();
//	Check_Object(light);
}

//------------------------------------------------------------------------------
//
void
	ShadowLight::GetInfo(gosFX::LightManager::Info *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::GetInfo(info);
//	MidLevelRenderer::MLRShadowLight *light = GetMLRLight();
//	Check_Object(light);
}

//------------------------------------------------------------------------------
//
CenterPointLight::CenterPointLight(MidLevelRenderer::MLRCenterPointLight *light):
	FiniteLight(light)
{
	Check_Pointer(this);
	Check_Object(light);

	gosFX::LightManager::Info info;
	CenterPointLight::GetInfo(&info);
	if (info.m_outer > 0.0f)
		CenterPointLight::ChangeLight(&info);
}

//############################################################################
//############################  LightManager  ################################
//############################################################################

TiledLightManager*
	&TiledLightManager::Instance=*Cast_Pointer(TiledLightManager**, &gosFX::LightManager::Instance);

TiledLightManager::ShadowMode
	TiledLightManager::s_ShadowMode=e_ArticulatedShadows;

static bool __stdcall CheckNoShadows()
{
	return TiledLightManager::s_ShadowMode==TiledLightManager::e_NoShadows;
}

static void __stdcall EnableNoShadows()
{
	TiledLightManager::s_ShadowMode = TiledLightManager::e_NoShadows;
}

static bool __stdcall CheckSimpleShadows()
{
	return TiledLightManager::s_ShadowMode==TiledLightManager::e_Shadows;
}

static void __stdcall EnableSimpleShadows()
{
	TiledLightManager::s_ShadowMode = TiledLightManager::e_Shadows;
}

static bool __stdcall CheckArticulatedShadows()
{
	return TiledLightManager::s_ShadowMode==TiledLightManager::e_ArticulatedShadows;
}

static void __stdcall EnableArticulatedShadows()
{
	TiledLightManager::s_ShadowMode = TiledLightManager::e_ArticulatedShadows;
}

//------------------------------------------------------------------------------
//
void
	TiledLightManager::InitializeClass(NotationFile *startup_ini)
{
	Verify(!Instance);
	gos_PushCurrentHeap(VideoRenderer::s_Heap);
	Instance = new TiledLightManager;
	gos_PopCurrentHeap();

#if !defined(NO_STATS)
	AddStatistic("Local Lights", "lights", gos_DWORD, &Light_Count, 0);
#endif

	Initialize_Timer(Shadow_Casting, "Shadow Casting");
	Initialize_Timer(Shadow_Start, "  Shadow Texture Start");
	Initialize_Timer(Shadow_Traversal, "  Shadow Traversal");
	Initialize_Timer(Shadow_End, "  Shadow Texture End");

	AddDebuggerMenuItem("Libraries\\Graphics Options\\No Shadows", CheckNoShadows, EnableNoShadows, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\Simple Shadows", CheckSimpleShadows, EnableSimpleShadows, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\Articulated Shadows", CheckArticulatedShadows, EnableArticulatedShadows, 0 );

	if (startup_ini)
	{
		Check_Object(startup_ini);
		Page *page = startup_ini->FindPage("Graphics Options");
		if (page)
		{
			Check_Object(page);
			int mode;
			if (page->GetEntry("ShadowMode", &mode))
				s_ShadowMode = static_cast<ShadowMode>(mode);
		}
	}
}

//------------------------------------------------------------------------------
//
void
	TiledLightManager::TerminateClass(NotationFile *startup_ini)
{

// MSL 5.02 Prevent Option Overwrite
// Prevents Software from Overwriting Options.ini Settings
//	if (startup_ini)
//	{
//		Check_Object(startup_ini);
//		Page *page = startup_ini->SetPage("Graphics Options");
//		Check_Object(page);
//		int mode = s_ShadowMode;
//		page->SetEntry("ShadowMode", mode);
//	}

	Check_Object(Instance);
	delete Instance;
	Instance = NULL;
}

//------------------------------------------------------------------------------
//
TiledLightManager::TiledLightManager():
	m_shadows(NULL)
{
}

//------------------------------------------------------------------------------
//
TiledLightManager::~TiledLightManager()
{
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeAmbientLight()
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRAmbientLight *mlr_light = new MidLevelRenderer::MLRAmbientLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	return new AmbientLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeInfiniteLight()
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRInfiniteLight *mlr_light = new MidLevelRenderer::MLRInfiniteLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	return new InfiniteLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeLookupLight()
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRLookUpLight *mlr_light = new MidLevelRenderer::MLRLookUpLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	return new InfiniteLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakePointLight(
		const char* light_map,
		bool light_vertex
	)
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRPointLight *mlr_light = new MidLevelRenderer::MLRPointLight;
	Check_Object(mlr_light);
	if (light_map)
	{
		MString name = light_map;
		Check_Object(MidLevelRenderer::MLRTexturePool::Instance);
		MidLevelRenderer::MLRTexture *texture =
			(*MidLevelRenderer::MLRTexturePool::Instance)(name, 0);
		if (!texture)
			texture = MidLevelRenderer::MLRTexturePool::Instance->Add(name, 0);
		Check_Object(texture);
		MidLevelRenderer::MLRLightMap *map = new MidLevelRenderer::MLRLightMap(texture);
		Check_Object(map);
		mlr_light->SetLightMap(map);
		if (!light_vertex)
		{
			int mask = mlr_light->GetLightMask();
			mask &= ~MidLevelRenderer::MLRState::VertexLightingMode;
			mlr_light->SetLightMask(mask);
		}
	}
	gos_PopCurrentHeap();
	return new PointLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeSpotLight(
		const char* light_map,
		bool light_vertex
	)
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRSpotLight *mlr_light = new MidLevelRenderer::MLRSpotLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	if (light_map)
	{
		MString name = light_map;
		Check_Object(MidLevelRenderer::MLRTexturePool::Instance);
		MidLevelRenderer::MLRTexture *texture =
			(*MidLevelRenderer::MLRTexturePool::Instance)(name, 0);
		if (!texture)
			texture = MidLevelRenderer::MLRTexturePool::Instance->Add(name, 0);
		Check_Object(texture);
		MidLevelRenderer::MLRLightMap *map = new MidLevelRenderer::MLRLightMap(texture);
		Check_Object(map);
		mlr_light->SetLightMap(map);
		if (!light_vertex)
		{
			int mask = mlr_light->GetLightMask();
			mask &= ~MidLevelRenderer::MLRState::VertexLightingMode;
			mlr_light->SetLightMask(mask);
		}
	}
	return new SpotLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeProjectLight(
		const char* light_map,
		bool light_vertex
	)
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRProjectLight *mlr_light = new MidLevelRenderer::MLRProjectLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	if (light_map)
	{
		MString name = light_map;
		Check_Object(MidLevelRenderer::MLRTexturePool::Instance);
		MidLevelRenderer::MLRTexture *texture =
			(*MidLevelRenderer::MLRTexturePool::Instance)(name, 0);
		if (!texture)
			texture = MidLevelRenderer::MLRTexturePool::Instance->Add(name, 0);
		Check_Object(texture);
		MidLevelRenderer::MLRLightMap *map = new MidLevelRenderer::MLRLightMap(texture);
		Check_Object(map);
		mlr_light->SetLightMap(map);
		if (!light_vertex)
		{
			int mask = mlr_light->GetLightMask();
			mask &= ~MidLevelRenderer::MLRState::VertexLightingMode;
			mlr_light->SetLightMask(mask);
		}
	}
	return new ProjectLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeShadowLight(
		const char* light_map,
		bool light_vertex
	)
{
	Check_Object(this);

	gos_PushCurrentHeap(MidLevelRenderer::LightsHeap);
	MidLevelRenderer::MLRShadowLight *mlr_light = new MidLevelRenderer::MLRShadowLight;
	Check_Object(mlr_light);
	gos_PopCurrentHeap();
	if (light_map)
	{
		MString name = light_map;
		Check_Object(MidLevelRenderer::MLRTexturePool::Instance);
		MidLevelRenderer::MLRTexture *texture =
			(*MidLevelRenderer::MLRTexturePool::Instance)(name, 0);
		if (!texture)
			texture = MidLevelRenderer::MLRTexturePool::Instance->Add(name, 0);
		Check_Object(texture);
		MidLevelRenderer::MLRLightMap *map = new MidLevelRenderer::MLRLightMap(texture);
		Check_Object(map);
		mlr_light->SetLightMap(map);
		if (!light_vertex)
		{
			int mask = mlr_light->GetLightMask();
			mask &= ~MidLevelRenderer::MLRState::VertexLightingMode;
			mlr_light->SetLightMask(mask);
		}
	}
	return new ShadowLight(mlr_light);
}

//------------------------------------------------------------------------------
//
gosFX::Light*
	TiledLightManager::MakeLight(MidLevelRenderer::MLRLight *light)
{
	Check_Object(this);

	if (light->IsDerivedFrom(MidLevelRenderer::MLRAmbientLight::DefaultData))
		return new AmbientLight(Cast_Object(MidLevelRenderer::MLRAmbientLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRLookUpLight::DefaultData))
		return new LookupLight(Cast_Object(MidLevelRenderer::MLRLookUpLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRInfiniteLight::DefaultData))
		return new InfiniteLight(Cast_Object(MidLevelRenderer::MLRInfiniteLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRSpotLight::DefaultData))
		return new SpotLight(Cast_Object(MidLevelRenderer::MLRSpotLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRPointLight::DefaultData))
		return new PointLight(Cast_Object(MidLevelRenderer::MLRPointLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRProjectLight::DefaultData))
		return new ProjectLight(Cast_Object(MidLevelRenderer::MLRProjectLight*, light));
	if (light->IsDerivedFrom(MidLevelRenderer::MLRShadowLight::DefaultData))
	{
		ShadowLight *shadow = new ShadowLight(Cast_Object(MidLevelRenderer::MLRShadowLight*, light));
		Check_Object(shadow);
		m_shadows.Add(shadow);
		return shadow;
	}
	STOP(("Non-supported light"));
	return NULL;
}

//------------------------------------------------------------------------------
//
void
	TiledLightManager::CastShadows(ElementRenderer::CameraElement *camera)
{
	Check_Object(this);

	//
	//------------------------------------------------------------------------
	// Go through the list of shadows, checking each light for its LOD setting
	//------------------------------------------------------------------------
	//
	if (s_ShadowMode == e_NoShadows)
		return;
	SHADOW_RENDER("");
	Start_Timer(ElementRenderer::Total_Video);
	Start_Timer(Shadow_Casting);
	const LinearMatrix4D &camera_to_world = camera->GetLocalToWorld(); 
	ChainIteratorOf<ShadowLight*> shadows(&m_shadows);
	ShadowLight *shadow;
	while ((shadow = shadows.ReadAndNext()) != NULL)
	{
		Check_Object(shadow);

		//
		//---------------------------------------------------------------------
		// Get the shadow information and set the shadow to the default mode of
		// blob
		//---------------------------------------------------------------------
		//
		MidLevelRenderer::MLRShadowLight *shadow_light = shadow->GetMLRLight();
		Check_Object(shadow_light);
		Info info;
		shadow->GetInfo(&info);
		shadow_light->SeeDetailShadow(false);
		Scalar blob_distance = shadow_light->GetBlobDistance();

		//
		//--------------------------------------------------------------------
		// Get the light information and compute the vector between the center
		// of the shadow and the eye (normalized to the x-z plane).  If we are
		// too far away, or if shadows are simple, don't articulate
		//--------------------------------------------------------------------
		//
		Vector3D dist(
			camera_to_world(3,0) - info.m_origin(3,0),
			0.0f,
			camera_to_world(3,2) - info.m_origin(3,2)
		);
		if (dist.GetLengthSquared() >= blob_distance*blob_distance || s_ShadowMode==e_Shadows)
			continue;

		//
		//---------------------------------------------------------
		// Make sure that the shadow is within the viewing frustrum
		//---------------------------------------------------------
		//
		Point3D center(info.m_origin);
		const Plane* plane = &camera->GetWorldCullingPlane(0);
		for (int i=0; i<ElementRenderer::CameraElement::CullingPlaneCount; ++i, ++plane)
		{
			Scalar distance = plane->GetDistanceTo(center);
			if (distance >= info.m_inner)
				break;
		}
		if (i<ElementRenderer::CameraElement::CullingPlaneCount)
			continue;
		//
		//-------------------------------------------------------------
		// Compute the direction of the sunlight from the lights origin
		//-------------------------------------------------------------
		//
		UnitVector3D sun_light;
		info.m_origin.GetLocalForwardInWorld(&sun_light);

		//
		// For some reason the shadow is becoming interesting too early and the origin of the light is not
		// synced. This can cause the y portion of the sun_light vector to be 0.0. So we are going to throw
		// out all cases where the sunlight is horizontal or up. This was causing div by 0 below.
		//
		if (sun_light.y >= -SMALL)
		  continue;

		//
		//-------------------------------------------------------------------
		// The shadow will be in the frustrum and close enough, so now set up
		// the renderer and the rasterizer to start drawing the object
		//-------------------------------------------------------------------
		//
		MidLevelRenderer::MLRState dState;
		camera->GetClipper()->StartDraw(
			Stuff::LinearMatrix4D::Identity,
			Stuff::Matrix4D::Identity,
			Stuff::RGBAColor::White,
			&Stuff::RGBAColor::White,
			dState,
			NULL
		);
		const MidLevelRenderer::MLRTexture *shadow_texture = shadow_light->GetShadowTexture();
		Check_Object(shadow_texture);
		{
			SHADOW_RENDER("Start Render");
			Start_Timer(Shadow_Start);
			gos_StartRenderToTexture( shadow_texture->GetImage()->GetHandle() );
			Stop_Timer(Shadow_Start);
		}

		//
		//---------------------------------------------------------------------
		// Compute where the sun would strike a x-z plane containing the origin
		// of the vehicle if the ray runs thru the center of the vehicle's
		// collision volume
		//---------------------------------------------------------------------
		//
		SHADOW_RENDER("Traversal");
		{
			Start_Timer(Shadow_Traversal);
			Entity *vehicle = Cast_Object(Entity*, shadow->m_shadowCaster->GetClientData());
			CollisionVolume *cv = vehicle->GetSolidVolume();

	#if defined(HUNT_LIGHT_CRASH) && defined(LAB_ONLY)
			
			if (cv == NULL)
			{
				if (vehicle->instanceName)
				{
					const char *name;
					name = vehicle->GetModelName ();

					STOP(("VEHICLE HAS NO CV : %s : %s", (char *)vehicle->instanceName, name));
				}
				else
				{
					const char *name;
					name = vehicle->GetModelName ();

					STOP(("VEHICLE HAS NO CV : noname : %s", name));
				}
			}
		
	#endif


			Check_Object(cv);
			LinearMatrix4D world_bounds;
			world_bounds.Multiply(cv->m_localSpaceBounds.localToParent, vehicle->GetLocalToWorld());
			Stuff::Point3D shadow_translation(world_bounds);
			Stuff::Point3D vehicle_translation(shadow->m_shadowCaster->GetLocalToWorld());
			Stuff::Scalar height = shadow_translation.y - vehicle_translation.y;
			shadow_translation.AddScaled(shadow_translation, sun_light, -height/sun_light.y);

			//
			//-------------------------------------------------------------------
			// The matrix will face the way the light did, but it will ignore any
			// pitch by aligning the local Y axis to the world Y axis
			//-------------------------------------------------------------------
			//
			Stuff::LinearMatrix4D shadow_plane_matrix(info.m_origin);
			shadow_plane_matrix.BuildTranslation(shadow_translation);
			shadow_plane_matrix.AlignLocalAxisToWorldVector(Stuff::Vector3D::Up, Y_Axis, X_Axis, Z_Axis);

			//
			//------------------------------------------------------
			// Now actually traverse the targeted object and draw it
			//------------------------------------------------------
			//
			shadow->m_shadowCaster->CastShadow(
				shadow_plane_matrix,
				sun_light,
				info.m_outer
			);
			Stop_Timer(Shadow_Traversal);
		}

		//
		//--------------
		// Now rasterize
		//--------------
		//
		{
			SHADOW_RENDER("End Render");
			Start_Timer(Shadow_End);
			gos_EndRenderToTexture(true);
			End3DScene();
			Stop_Timer(Shadow_End);
		}

		//
		//-----------------------------------------------------------------
		// Set up some parameters for the shadow - the falloff distance bit
		// is flaky
		//-----------------------------------------------------------------
		//
		shadow_light->SeeDetailShadow(true);
	}
	Stop_Timer(Shadow_Casting);
	Stop_Timer(ElementRenderer::Total_Video);
}
