#include "ElementRendererHeaders.hpp"
#include <MLR\MLRInfiniteLightWithFalloff.hpp>

//#define HUNT_FOR_THE_MISSING_SHADOW "micgaert"

#if defined (LAB_ONLY) && defined (HUNT_FOR_THE_MISSING_SHADOW)
#include <MLR\MLRTexturePool.hpp>
#endif

int ElementRenderer::ShapeElement::s_MaxLightsPerShape=MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive;

static bool __stdcall Check2() {return ElementRenderer::ShapeElement::s_MaxLightsPerShape == 2;}
static bool __stdcall Check4() {return ElementRenderer::ShapeElement::s_MaxLightsPerShape == 4;}

static void __stdcall Enable2()
{
	ElementRenderer::ShapeElement::s_MaxLightsPerShape = (ElementRenderer::ShapeElement::s_MaxLightsPerShape == 2) ? MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive : 2;
}
static void __stdcall Enable4()
{
	ElementRenderer::ShapeElement::s_MaxLightsPerShape = (ElementRenderer::ShapeElement::s_MaxLightsPerShape == 4) ? MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive : 4;
}

bool ElementRenderer::ShapeElement::s_ShowClippedBounds = false;
static bool __stdcall Greyed() {return ElementRenderer::Element::GetCallbackSet() != NULL;}
static bool __stdcall Check_ShowShapeBounds() {return ElementRenderer::ShapeElement::s_ShowClippedBounds;}
static void __stdcall Activate_ShowShapeBounds() {ElementRenderer::ShapeElement::s_ShowClippedBounds = !ElementRenderer::ShapeElement::s_ShowClippedBounds;}

bool ElementRenderer::ShapeElement::s_CheckBounds = false;
bool ElementRenderer::ShapeElement::s_BoundsWereBad = false;
static bool __stdcall Check_CheckBounds() {return ElementRenderer::ShapeElement::s_CheckBounds;}
static void __stdcall Activate_CheckBounds() {ElementRenderer::ShapeElement::s_CheckBounds = !ElementRenderer::ShapeElement::s_CheckBounds;}

//############################################################################
//############################  ShapeElement  ################################
//############################################################################

HGOSHEAP ElementRenderer::ShapeElement::s_Heap = NULL;

ElementRenderer::ShapeElement::ClassData* ElementRenderer::ShapeElement::DefaultData = NULL;

ElementRenderer::Element::SyncMethod ElementRenderer::ShapeElement::SyncMethods[SyncStateCount]=
{
	//
	// Root mode
	//
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	DirtyRoot),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	DirtyRoot),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	DirtyRoot),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	DirtyRoot),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Sphere Cull
	//
	SYNC_METHOD(ShapeElement,	CleanSphere),
	SYNC_METHOD(ShapeElement,	DirtySphere),
	SYNC_METHOD(ShapeElement,	CleanSphere),
	SYNC_METHOD(ShapeElement,	DirtySphere),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// OBB Cull
	//
	SYNC_METHOD(ShapeElement,	CleanOBB),
	SYNC_METHOD(ShapeElement,	DirtyOBB),
	SYNC_METHOD(ShapeElement,	CleanOBB),
	SYNC_METHOD(ShapeElement,	DirtyOBB),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Never Cull
	//
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	Dirty),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	Dirty),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	Dirty),
	SYNC_METHOD(ShapeElement,	Clean),
	SYNC_METHOD(ShapeElement,	Dirty),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Always Cull
	//
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, None)
};

ElementRenderer::Element::DrawMethod ElementRenderer::ShapeElement::DrawMethods[DrawStateCount]=
{
	//
	// No billboarding
	//
	DRAW_METHOD(ShapeElement,	Inherit),
	DRAW_METHOD(ShapeElement,	Override)
};

DECLARE_TIMER(static, Light_Culling_Time);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::InitializeClass(Stuff::NotationFile *startup_ini)
{
	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("Shape", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			ShapeElementClassID,
			"ElementRenderer::ShapeElement",
			BaseClass::DefaultData,
			reinterpret_cast<Factory>(&Make)
		);
	Check_Object(DefaultData);

	s_MaxLightsPerShape = MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive;
	s_ShowClippedBounds = false;
	s_CheckBounds = false;
	s_BoundsWereBad = false;

	Initialize_Timer(Light_Culling_Time, "Light Culling");
	AddDebuggerMenuItem("Libraries\\Graphics Options\\2 Lights/Shape", Check2, Enable2, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\4 Lights/Shape", Check4, Enable4, 0 );
	AddDebuggerMenuItem("Libraries\\ElementRenderer\\Show Clipped Shape Bounds", Check_ShowShapeBounds, Activate_ShowShapeBounds, Greyed );
	AddDebuggerMenuItem("Libraries\\ElementRenderer\\Check Bounds", Check_CheckBounds, Activate_CheckBounds, NULL );

	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->FindPage("Graphics Options");
		if (page)
		{
			Check_Object(page);
			page->GetEntry("MaxLights", &s_MaxLightsPerShape);
			Min_Clamp(s_MaxLightsPerShape, 2);
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::TerminateClass(Stuff::NotationFile *startup_ini)
{
	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->SetPage("Graphics Options");
		Check_Object(page);
		page->SetEntry("MaxLights", s_MaxLightsPerShape);
	}

	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

	Check_Pointer(s_Heap);
	gos_DestroyMemoryHeap(s_Heap);
	s_Heap = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ShapeElement::ShapeElement(
	ClassData *class_data,
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
):
	Element(class_data, stream, version)
{
	Check_Pointer(this);
	Check_Object(stream);

	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	//
	//---------------------------------
	// Create the shape from the stream
	//---------------------------------
	//
	m_worldToLocal = Stuff::LinearMatrix4D::Identity;
	int mlr_version = MidLevelRenderer::ReadMLRVersion(stream);
	if (mlr_version == -1)
		mlr_version = version;

	if (version<13)
	{
		if(mlr_version<14)
			m_MLRShape = MidLevelRenderer::MLRShape::Make(stream, mlr_version);
		else
		{
			RegisteredClass::ClassID class_id;
			*stream >> class_id;

			switch(class_id)
			{
				case MidLevelRenderer::MLRShapeClassID:
					m_MLRShape = MidLevelRenderer::MLRShape::Make(stream, mlr_version);
				break;
				case MidLevelRenderer::MLRCulturShapeClassID:
					m_MLRShape = MidLevelRenderer::MLRCulturShape::Make(stream, mlr_version);
				break;
				default:
					STOP(("Don't know what this is"));
			}
		}
	}
	else
	{
		int length;
		*stream >> length;
		if (shapes)
			m_MLRShape = (*shapes)(stream, mlr_version, length);
		else
		{
			RegisteredClass::ClassID class_id;
			*stream >> class_id;
			switch(class_id)
			{
				case MidLevelRenderer::MLRShapeClassID:
					m_MLRShape = MidLevelRenderer::MLRShape::Make(stream, mlr_version);
				break;
				case MidLevelRenderer::MLRCulturShapeClassID:
					m_MLRShape = MidLevelRenderer::MLRCulturShape::Make(stream, mlr_version);
				break;
				default:
					STOP(("Don't know what this is"));
			}
		}
	}

	//
	//------------------------------------------------
	// Bump the OBB a tad to help with clipping errors
	//------------------------------------------------
	//
	if (IsBoundedBySphere())
		m_localOBB.sphereRadius *= 1.01f;
	else
	{
		m_localOBB.axisExtents *= 1.01f;
		m_localOBB.sphereRadius = m_localOBB.axisExtents.GetLength();
	}
	if (s_CheckBounds && !CheckBounds())
		s_BoundsWereBad = true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ShapeElement::ShapeElement(
	ShapeElement *shape,
	ClassData *class_data
):
	Element(class_data, shape)
{
	Check_Pointer(this);

	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	m_worldToLocal = Stuff::LinearMatrix4D::Identity;
	Check_Object(shape->m_MLRShape);
	m_MLRShape = shape->m_MLRShape;
	m_MLRShape->AttachReference();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ShapeElement::ShapeElement(ClassData *class_data):
	Element(class_data)
{
	Check_Pointer(this);

	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	m_worldToLocal = Stuff::LinearMatrix4D::Identity;

	gos_PushCurrentHeap(MidLevelRenderer::ShapeHeap);
	m_MLRShape = new MidLevelRenderer::MLRShape(0);
	Check_Object(m_MLRShape);
	gos_PopCurrentHeap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ShapeElement::~ShapeElement()
{
	Check_Object(this);
	m_MLRShape->DetachReference();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ShapeElement* ElementRenderer::ShapeElement::Make(
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
)
{
	Check_Object(stream);

	gos_PushCurrentHeap(s_Heap);
	ShapeElement *element = new ShapeElement(DefaultData, stream, version, shapes);
	gos_PopCurrentHeap();

	return element;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);
	BaseClass::Save(stream);

	//
	//-----------------------------
	// Save the shape to the stream
	//-----------------------------
	//
	Check_Object(m_MLRShape);
	MidLevelRenderer::WriteMLRVersion(stream);
	*stream << (int)0;
	unsigned index = stream->GetIndex();
	m_MLRShape->Save(stream);
	unsigned length = stream->GetIndex() - index;
	stream->RewindPointer(length + sizeof(int));
	*stream << length;
	stream->AdvancePointer(length);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool ElementRenderer::ShapeElement::CheckBounds()
{
	Check_Object(this);

	return (IsBoundedBySphere()) ? CheckSphereBounds(m_MLRShape, m_localOBB) : CheckOBBBounds(m_MLRShape, m_localOBB);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool ElementRenderer::ShapeElement::CheckSphereBounds(
	MidLevelRenderer::MLRShape *shape,
	const Stuff::OBB &sphere
)
{
	Stuff::Point3D center(sphere.localToParent);
	Stuff::Scalar radius_squared = sphere.sphereRadius*sphere.sphereRadius;
	int meshes = shape->GetNum();
	for (int m=0; m<meshes; m++)
	{
		MidLevelRenderer::MLRPrimitiveBase *mesh = shape->Find(m);
		Check_Object(mesh);
		const Stuff::Point3D *verts;
		int count;
		mesh->GetCoordData(&verts, &count);
		for (int v=0; v<count; ++v)
		{
			Stuff::Vector3D diff;
			diff.Subtract(verts[v], center);
			if (diff.GetLengthSquared() > radius_squared)
				return false;
		}
	}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool ElementRenderer::ShapeElement::CheckOBBBounds(
	MidLevelRenderer::MLRShape *shape,
	const Stuff::OBB &obb
)
{
	int meshes = shape->GetNum();
	for (int m=0; m<meshes; m++)
	{
		MidLevelRenderer::MLRPrimitiveBase *mesh = shape->Find(m);
		Check_Object(mesh);
		const Stuff::Point3D *verts;
		int count;
		mesh->GetCoordData(&verts, &count);
		for (int v=0; v<count; ++v)
		{
			if (!obb.Contains(verts[v]))
				return false;
		}
	}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::AttachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);
	STOP(("Shape elements can't have children!"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::DetachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);
	STOP(("Shape elements can't have children!"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::SetSyncState()
{
	Check_Object(this);

	Verify(
		GetCullMode() != VolumeCullMode
		 || AreBoundsWrong()
		 || IsSyncDeferred()
		 || (
				m_localOBB.sphereRadius>0.0f
				 && (IsMatrixDirty() || m_worldOBB.sphereRadius>0.0f
			)
		)
	);
	Verify(
		GetCullMode() == RootMode || m_parent != NULL || AreBoundsWrong()
	);
	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::SetDrawState()
{
	Check_Object(this);
	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::DirtyRootSyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty() && GetCullMode()==RootMode);
	SYNC_LOGIC("Root::Dirty::Shape");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_localToWorld = m_newLocalToParent;
	m_worldToLocal.Invert(m_localToWorld);
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::DirtySyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty() && GetCullMode()==NeverCullMode);
	SYNC_LOGIC("Dirty::Shape");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	m_worldToLocal.Invert(m_localToWorld);
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::CleanSphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		!IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Clean::Shape");

	//
	//-------------------------------
	// Update our m_localToWorld matrix
	//-------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	m_worldToLocal.Invert(m_localToWorld);

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::DirtySphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Dirty::Shape");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	m_worldToLocal.Invert(m_localToWorld);

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::CleanOBBSyncMethod()
{
	Check_Object(this);
	Verify(
		!IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedByOBB()
	);
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));
	SYNC_LOGIC("OBB::Clean::Shape");

	//
	//-------------------------------
	// Update our m_localToWorld matrix
	//-------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	m_worldToLocal.Invert(m_localToWorld);

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.Multiply(m_localOBB, m_localToWorld);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::DirtyOBBSyncMethod()
{
	Check_Object(this);
	Verify(
		IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedByOBB()
	);
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));
	SYNC_LOGIC("OBB::Dirty::Shape");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	m_worldToLocal.Invert(m_localToWorld);

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.Multiply(m_localOBB, m_localToWorld);
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool ElementRenderer::ShapeElement::CastCulledRay(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(!query->m_data);

	//
	//-----------------------------------
	// Figure out the local m_line equation
	//-----------------------------------
	//
	const Stuff::LinearMatrix4D &world_to_local = GetWorldToLocal();
	Check_Object(&world_to_local);
	Stuff::Line3D local_line;
	Check_Object(query->m_line);
	local_line.m_origin.Multiply(query->m_line->m_origin, world_to_local);
	local_line.m_direction.Multiply(query->m_line->m_direction, world_to_local);
	local_line.m_length = query->m_line->m_length;

	//
	//-----------------------------
	// Test it against the MidLevelRenderer::MLRShape
	//-----------------------------
	//
	Stuff::Normal3D local_normal;
	if (m_MLRShape)
	{
		Check_Object(m_MLRShape);
		if (m_MLRShape->CastRay(&local_line, &local_normal))
		{
			query->m_line->m_length = local_line.m_length;
			const Stuff::LinearMatrix4D &local_to_world = GetLocalToWorld();
			Check_Object(&local_to_world);
			Check_Pointer(query->m_normal);
			query->m_normal->Multiply(local_normal, local_to_world);
			Verify(*query->m_normal * query->m_line->m_direction <= -Stuff::SMALL);
			query->m_data = m_data;
			return true;
		}
	}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element* ElementRenderer::ShapeElement::FindSmallestElementContainingCulled(SphereTest *test)
{
	Check_Object(this);
	Check_Object(test);
	return this;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::InheritDrawMethod(
	CameraElement *camera,
	const StateChange *inherited_state,
	int culling_state
)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);
	#if defined(_ARMOR)
		if (!culling_state)
			Verify(!Cull(camera));
	#endif

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
	#if defined(LAB_ONLY)
		Callback *callback = GetCallbackSet();
		unsigned index = GetCallbackIndex();
		if (callback && callback[index])
		{
			Check_Pointer(callback[index]);
			(*callback[index])(camera, inherited_state, culling_state, this);
		}
		else if (s_ShowClippedBounds && GetCullMode() == VolumeCullMode && culling_state)
		{
			Stuff::RGBAColor color(1.0f, 0.5f, 0.5f, 0.3f);
			if (IsBoundedBySphere())
				DrawSphere(m_worldOBB, color, camera, inherited_state, culling_state);
			else
				DrawOBB(m_worldOBB, color, camera, inherited_state, culling_state);
		}
	#endif

	//
	//----------------------------
	// Set up the draw information
	//----------------------------
	//
	MidLevelRenderer::DrawShapeInformation info;
	Check_Object(m_MLRShape);
	info.shape = m_MLRShape;
	info.clippingFlags.SetClippingState(culling_state);
	Check_Object(&m_localToWorld);
	info.shapeToWorld = &m_localToWorld;
	Check_Object(&m_worldToLocal);
	info.worldToShape = &m_worldToLocal;
	info.state = inherited_state->GetMLRState();
	info.paintMeColor = &FadeColor;

#if defined (LAB_ONLY) && defined (HUNT_FOR_THE_MISSING_SHADOW)
	if(m_MLRShape->GetNum()>0)
	{
		MidLevelRenderer::MLRTexture *tex = (*MidLevelRenderer::MLRTexturePool::Instance)[&m_MLRShape->Find(0)->GetReferenceState()];
		MidLevelRenderer::GOSImage *image = tex->GetImage();
		const char *tex_name = gos_GetTextureName(image->GetHandle());

		SPEW((HUNT_FOR_THE_MISSING_SHADOW, "D: %s", tex_name));
	}
#endif

	LightList culled_lights;
	info.nrOfActiveLights = CullLights(culled_lights, inherited_state->m_lights);
	info.activeLights = (info.nrOfActiveLights>0) ? &culled_lights[0] : NULL;

	//
	//---------------
	// Draw the shape
	//---------------
	//
	MidLevelRenderer::MLRClipper *clipper = camera->GetClipper();
	Check_Object(clipper);

	Stop_Timer(Graph_Traversal);
	ELEMENT_RENDER("Culling::Shape::Inherited");
	Start_Timer(Draw_Shapes);
	clipper->DrawShape(&info);
	Stop_Timer(Draw_Shapes);
	Start_Timer(Graph_Traversal);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::OverrideDrawMethod(
	CameraElement *camera,
	const StateChange *inherited_state,
	int culling_state
)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);
	#if defined(_ARMOR)
		if (!culling_state)
			Verify(!Cull(camera));
	#endif

	//
	//-------------------
	// Mix the properties
	//-------------------
	//
	Check_Object(m_stateChange);
	StateChange mixed;
	mixed.Mix(*inherited_state, *m_stateChange);
	MixLights(
		mixed.m_lights,
		inherited_state->m_lights,
		m_stateChange->m_lights
	);

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
	#if defined(LAB_ONLY)
		Callback *callback = GetCallbackSet();
		unsigned index = GetCallbackIndex();
		if (callback && callback[index])
		{
			Check_Pointer(callback[index]);
			(*callback[index])(camera, &mixed, culling_state, this);
		}
		else if (s_ShowClippedBounds && GetCullMode() == VolumeCullMode && culling_state)
		{
			Stuff::RGBAColor color(1.0f, 0.5f, 0.5f, 0.3f);
			if (IsBoundedBySphere())
				DrawSphere(m_worldOBB, color, camera, &mixed, culling_state);
			else
				DrawOBB(m_worldOBB, color, camera, &mixed, culling_state);
		}
	#endif

	//
	//----------------------------
	// Set up the draw information
	//----------------------------
	//
	MidLevelRenderer::DrawShapeInformation info;
	Check_Object(m_MLRShape);
	info.shape = m_MLRShape;
	info.clippingFlags.SetClippingState(culling_state);
	Check_Object(&m_localToWorld);
	info.shapeToWorld = &m_localToWorld;
	Check_Object(&m_worldToLocal);
	info.worldToShape = &m_worldToLocal;
	info.state = mixed.GetMLRState();
	info.paintMeColor = &FadeColor;
	LightList culled_lights;

#if defined (LAB_ONLY) && defined (HUNT_FOR_THE_MISSING_SHADOW)
	MidLevelRenderer::MLRTexture *tex = (*MidLevelRenderer::MLRTexturePool::Instance)[&m_MLRShape->Find(0)->GetReferenceState()];
	MidLevelRenderer::GOSImage *image = tex->GetImage();
	const char *tex_name = gos_GetTextureName(image->GetHandle());

	SPEW((HUNT_FOR_THE_MISSING_SHADOW, "D: %s", tex_name));
#endif

	info.nrOfActiveLights = CullLights(culled_lights, mixed.m_lights);
	info.activeLights = (info.nrOfActiveLights>0) ? &culled_lights[0] : NULL;

	//
	//---------------
	// Draw the shape
	//---------------
	//
	MidLevelRenderer::MLRClipper *clipper = camera->GetClipper();
	Check_Object(clipper);

	Stop_Timer(Graph_Traversal);
	ELEMENT_RENDER("Culling::Shape::Overridden");
	Start_Timer(Draw_Shapes);
	clipper->DrawShape(&info);
	Stop_Timer(Draw_Shapes);
	Start_Timer(Graph_Traversal);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::SetMLRShape(MidLevelRenderer::MLRShape *shape)
{
	Check_Object(this);
	Check_Object(shape);

	if (m_MLRShape)
	{
		Check_Object(m_MLRShape);
		m_MLRShape->DetachReference();
	}
	m_MLRShape = shape;
	m_MLRShape->AttachReference();
}

extern DWORD Disable_Lights;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int ElementRenderer::ShapeElement::CullLights(
	LightList culled_lights,
	const LightList lights
)
{
	Check_Object(this);

	//
	//----------------------------------------------------------------
	// Set up the destination light array and start copying the lights
	//----------------------------------------------------------------
	//
	if (Disable_Lights)
		return 0;

	ELEMENT_RENDER("Culling::ShapeLOD::Lights");
	Start_Timer(Light_Culling_Time);
	int new_count = 0;
	Stuff::Point3D center(m_worldOBB.localToParent);
	for (int i=0; i<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive && lights[i]; ++i)
	{
		MidLevelRenderer::MLRLight *light = lights[i];
		Check_Object(light);
		Verify(new_count < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
		if (light->GetIntensity() < Stuff::SMALL)
			continue;

		//
		//--------------------------------------------------------------------
		// If we are copying a light with a range, check it against the bounds
		// of this node and only copy if it would hit the bounds
		//--------------------------------------------------------------------
		//
		if (light->IsDerivedFrom(MidLevelRenderer::MLRInfiniteLightWithFalloff::DefaultData))
		{
			MidLevelRenderer::MLRInfiniteLightWithFalloff *bounded_light =
				Cast_Object(MidLevelRenderer::MLRInfiniteLightWithFalloff*, light);
			Stuff::Point3D lc(bounded_light->GetLightToWorldMatrix());
			Stuff::Vector3D diff;
			diff.Subtract(center, lc);
			Stuff::Scalar range =
				m_worldOBB.sphereRadius + bounded_light->GetFalloffFar();
			if (diff.GetLengthSquared() < range*range)
				culled_lights[new_count++] = light;
		}

		//
		//---------------------------------
		// Otherwise it is automatically in
		//---------------------------------
		//
		else
			culled_lights[new_count++] = light;
		if (new_count >= s_MaxLightsPerShape)
		{
#if defined (LAB_ONLY) && defined (HUNT_FOR_THE_MISSING_SHADOW)
			SPEW((HUNT_FOR_THE_MISSING_SHADOW, "L: %d+", new_count));
#endif
			break;
		}
	}
#if defined (LAB_ONLY) && defined (HUNT_FOR_THE_MISSING_SHADOW)
		SPEW((HUNT_FOR_THE_MISSING_SHADOW, "L: %d", new_count));
#endif
	Verify(new_count <= MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
	Stop_Timer(Light_Culling_Time);
	return new_count;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int ElementRenderer::ShapeElement::CountTriangles()
{
	Check_Object(this);

	if (m_MLRShape)
	{
		Check_Object(m_MLRShape);
		return m_MLRShape->GetNumPrimitives();
	}
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::CleanDamage()
{
	Check_Object(this);

	Check_Object(m_MLRShape);
	Verify(m_MLRShape->GetReferenceCount() == 1);
	m_MLRShape->CleanMe();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::ApplyDamage(
	const Stuff::LinearMatrix4D &damage_spot,
	Stuff::Scalar radius
)
{
	Check_Object(this);
	Check_Object(&damage_spot);

	Check_Object(m_MLRShape);
	Verify(m_MLRShape->GetReferenceCount() == 1);
	m_MLRShape->HurtMe(damage_spot, radius);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::ApplyDamageDecal(
	const Stuff::LinearMatrix4D &damage_spot,
	Stuff::Scalar radius,
	MidLevelRenderer::MLRTexture *texture
)
{
	Check_Object(this);
	Check_Object(&damage_spot);

	Check_Object(m_MLRShape);
	Verify(m_MLRShape->GetReferenceCount() == 1);
	m_MLRShape->HurtMe(damage_spot, radius, texture);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::CastShadow(
	const Stuff::LinearMatrix4D &shadow_to_world,
	const Stuff::UnitVector3D &sun_in_world,
	Stuff::Scalar radius
)
{
	if (!IsShadowed())
		return;
	MidLevelRenderer::MLRShape *mlr_shape = GetMLRShape();	
	Stuff::LinearMatrix4D world_to_object;
	world_to_object.Invert(GetLocalToWorld());
	Stuff::UnitVector3D sun_in_object;
	sun_in_object.Multiply(sun_in_world, world_to_object);
	Stuff::LinearMatrix4D shadow_to_object;
	shadow_to_object.Multiply(shadow_to_world, world_to_object);

	mlr_shape->DrawMyShadow(shadow_to_object, sun_in_object, radius);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::ShapeElement::MakeFootStep(
	MidLevelRenderer::MLRShape *shape,
	const Stuff::LinearMatrix4D &foot,
	Stuff::Scalar radius,
	MidLevelRenderer::MLRTexture *tex
)
{
	MidLevelRenderer::MLRShape *mlr_shape = GetMLRShape();	

	Stuff::LinearMatrix4D world_to_object;
	world_to_object.Invert(GetLocalToWorld());

	Stuff::LinearMatrix4D foot_to_object;
	foot_to_object.Multiply(foot, world_to_object);

	mlr_shape->StepOnMe(shape, foot_to_object, radius, tex);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	Returns array of vertex coordinates and number of vertices
void ElementRenderer::ShapeElement::GetCoordData(Stuff::DynamicArrayOf<Stuff::Point3D> *array)
{
	Check_Object(this);
	array->SetLength(0);
	
	int meshes = m_MLRShape->GetNum();
	for(int m=0; m<meshes; m++)
	{
		MidLevelRenderer::MLRPrimitiveBase *mesh = m_MLRShape->Find(m);
		Check_Object(mesh);
		const Stuff::Point3D *verts;
		int count;
		mesh->GetCoordData(&verts, &count);
		
		int point_count = array->GetLength();
		array->SetLength(point_count+count);
		for (int v=0; v<count; ++v)
		{
			(*array)[point_count+v]=verts[v];
		}
	}
}
