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

//#define SPEW_CULL_INFO "your_name_here"

//############################################################################
//############################  TreeElement  ################################
//############################################################################

HGOSHEAP ElementRenderer::TreeElement::s_Heap = NULL;

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

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

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

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

	//
	// Never Cull
	//
	SYNC_METHOD(TreeElement,	Clean),
	SYNC_METHOD(TreeElement,	Dirty),
	SYNC_METHOD(TreeElement,	Clean),
	SYNC_METHOD(TreeElement,	Dirty),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(TreeElement,	Clean),
	SYNC_METHOD(TreeElement,	Dirty),
	SYNC_METHOD(TreeElement,	Clean),
	SYNC_METHOD(TreeElement,	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::TreeElement::DrawMethods[DrawStateCount]=
{
	DRAW_METHOD(TreeElement,	Inherit),
	DRAW_METHOD(TreeElement,	Override)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::InitializeClass()
{
	Verify(!DefaultData);

	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("TreeLOD", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

	DefaultData =
		new ClassData(
			TreeElementClassID,
			"ElementRenderer::TreeElement",
			BaseClass::DefaultData,
			reinterpret_cast<Factory>(&Make)
		);
	Check_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::TerminateClass()
{
	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

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

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

	if (version<14)
		STOP(("Application must be recompiled to use this data!"));
	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];

	//
	//----------------------
	// Read in the LOD count
	//----------------------
	//
	m_worldToLocal = Stuff::LinearMatrix4D::Identity;
	WORD count;
	*stream >> m_startAligningAt >> m_alignType >> count;
	m_ranges.SetLength(count);
	m_MLRShapes.SetLength(count);
	int mlr_version = MidLevelRenderer::ReadMLRVersion(stream);

	//
	//------------------------------------------------------------------------
	// Newer versions only load in the parts of the Multi-LOD that are visible
	//------------------------------------------------------------------------
	//
	WORD used=0;
	for (WORD i=0; i<count; ++i)
	{
		*stream >> m_ranges[used].m_nearSquared >> m_ranges[used].m_fadeInSquared;
		*stream >> m_ranges[used].m_fadeOutSquared >> m_ranges[used].m_farSquared;

		//
		//-----------------------------------------------------
		// If this can't be seen because of the offset, dump it
		//-----------------------------------------------------
		//
		int length;
		*stream >> length;
		if (i<count-1 && LODElement::s_Offset > m_ranges[used].m_farSquared)
		{
			stream->AdvancePointer(length);
			continue;
		}

		//
		//------------------------------------------------------------------------------------
		// If this is the first visible LOD after we have dropped one, reset the near clipping
		// plane to zero
		//------------------------------------------------------------------------------------
		//
		else if (!used && i>0)
		{
			m_ranges[used].m_nearSquared = 0.0f;
			m_ranges[used].m_fadeInSquared = 0.0f;
		}

		//
		//-------------------------------------------------------------------
		// If we were given a shape holder, have it make the MLR shape for us
		//-------------------------------------------------------------------
		//
		if (shapes)
			m_MLRShapes[used] = (*shapes)(stream, mlr_version, length);

		//
		//---------------------------------------------------------------
		// Otherwise, read in the class ID and call the right constructor
		//---------------------------------------------------------------
		//
		else
		{
			RegisteredClass::ClassID class_id;
			*stream >> class_id;
			switch(class_id)
			{
				case MidLevelRenderer::MLRShapeClassID:
					m_MLRShapes[used] = MidLevelRenderer::MLRShape::Make(stream, mlr_version);
				break;
				case MidLevelRenderer::MLRCulturShapeClassID:
					m_MLRShapes[used] = MidLevelRenderer::MLRCulturShape::Make(stream, mlr_version);
				break;
			}
		}
		Check_Object(m_MLRShapes[used]);
		++used;
	}
	Verify(used>0);
	if (used < count)
	{
		m_ranges.SetLength(used);
		m_MLRShapes.SetLength(used);
	}

	//
	//------------------------------------------------
	// 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 (ShapeElement::s_CheckBounds && !CheckBounds())
		ShapeElement::s_BoundsWereBad = true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::TreeElement::TreeElement():
	Element(DefaultData)
{
	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;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::TreeElement::~TreeElement()
{
	Check_Object(this);
	for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
	{
		Check_Object(m_MLRShapes[i]);
		m_MLRShapes[i]->DetachReference();
	}
}

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

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

	return element;
}

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

	//
	//-----------------------------
	// Save the shape to the stream
	//-----------------------------
	//
	Verify(m_ranges.GetLength() < 65536);
	*stream << m_startAligningAt << m_alignType << static_cast<WORD>(m_ranges.GetLength());
	MidLevelRenderer::WriteMLRVersion(stream);
	for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
	{
		Check_Object(m_MLRShapes[i]);

		//
		//------------------------
		// Save out the LOD ranges
		//------------------------
		//
		*stream << m_ranges[i].m_nearSquared << m_ranges[i].m_fadeInSquared;
		*stream << m_ranges[i].m_fadeOutSquared << m_ranges[i].m_farSquared;

		//
		//-------------------------------------------------------------------------------
		// Figure out how much space this particular MLRShape uses, and put that into the
		// length pointer
		//-------------------------------------------------------------------------------
		//
		*stream << (int)0;
		unsigned index = stream->GetIndex();
		m_MLRShapes[i]->Save(stream);
		unsigned length = stream->GetIndex() - index;
		stream->RewindPointer(length + sizeof(int));
		*stream << length;
		stream->AdvancePointer(length);
	}
}

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

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

	int meshes = m_MLRShapes.GetLength();
	for(int m=0; m<meshes; m++)
	{
		bool check;
		if (IsBoundedBySphere())
			check = ShapeElement::CheckSphereBounds(m_MLRShapes[m], m_localOBB);
		else
			check = ShapeElement::CheckOBBBounds(m_MLRShapes[m], m_localOBB);
		if (!check)
			return false;
	}
	return true;
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::AttachLOD(
	WORD index,
	MidLevelRenderer::MLRShape *shape,
	const Entry &entry
)
{
	Check_Object(this);
	Check_Object(shape);

	if (m_MLRShapes[index])
	{
		Check_Object(m_MLRShapes[index]);
		m_MLRShapes[index]->DetachReference();
	}
	m_MLRShapes[index] = shape;
	m_MLRShapes[index]->AttachReference();
	m_ranges[index] = entry;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  This is necessary for attaching LOD's into the list that might miss certain 
// meshes in the middle ie.. 4/5 of the LOD's have a toe, but lod 2 doesn't
// Needed for the exporter. The grouping there doesn't allow counters.
int ElementRenderer::TreeElement::AttachLODFirstAvailableSlot(
	MidLevelRenderer::MLRShape *shape,
	const Entry &entry
)
{
	Check_Object(this);
	Check_Object(shape);

	for (unsigned short index = 0; index < m_MLRShapes.GetLength(); index ++)
	{

		if (m_MLRShapes[index] == NULL)
		{
			m_MLRShapes[index] = shape;
			m_MLRShapes[index]->AttachReference();
			m_ranges[index] = entry;
			return index;
		}

	}
	STOP(("TreeElement: Can't attach another LOD, already full!"));
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::GetLOD(
	WORD index,
	MidLevelRenderer::MLRShape **shape,
	const Entry **ranges
)
{
	Check_Object(this);

	Check_Object(m_MLRShapes[index]);
	*shape = m_MLRShapes[index];
	(*shape)->AttachReference();
	*ranges = &m_ranges[index];
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::SetLOD(
	WORD index,
	const Entry &ranges
)
{
	Check_Object(this);
	m_ranges[index]=ranges;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::SetSize(WORD max_size)
{
	Check_Object(this);
	DWORD i = m_MLRShapes.GetLength();
	m_MLRShapes.SetLength(max_size);
	m_ranges.SetLength(max_size);
	for (;i<max_size; ++i)
	{
		m_MLRShapes[i] = NULL;
		m_ranges[i].m_nearSquared = 0.0f;
		m_ranges[i].m_fadeInSquared = 0.0f;
		m_ranges[i].m_fadeOutSquared = 0.0f;
		m_ranges[i].m_farSquared = 0.0f;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
WORD ElementRenderer::TreeElement::GetSize()
{
	Check_Object(this);
	Verify(m_MLRShapes.GetLength() < 65536);
	return static_cast<WORD>(m_MLRShapes.GetLength());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::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::TreeElement::DirtySyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty() && GetCullMode()==NeverCullMode);
	SYNC_LOGIC("Dirty::Tree");

	//
	//--------------------
	// 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::TreeElement::CleanSphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		!IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Clean::Tree");

	//
	//-------------------------------
	// 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::TreeElement::DirtySphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Dirty::Tree");

	//
	//--------------------
	// 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::TreeElement::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::Tree");

	//
	//-------------------------------
	// 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::TreeElement::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::Tree");

	//
	//--------------------
	// 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::TreeElement::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_MLRShapes.GetLength()>0)
	{
		Check_Object(m_MLRShapes[0]);
		if (m_MLRShapes[0]->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::TreeElement::FindSmallestElementContainingCulled(SphereTest *test)
{
	Check_Object(this);
	Check_Object(test);
	return this;
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::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 (ShapeElement::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

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + LODElement::s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//-------------------------------------------------
	// Evaluate each LOD to see if it needs to be drawn
	//-------------------------------------------------
	//
	for (int i=m_ranges.GetLength()-1; i>=0; --i)
	{
		Entry *entry = &m_ranges[i];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{

			//
			//-----------------------------------------
			// Set the fade color if we are in the band
			//-----------------------------------------
			//
			StateChange mixed(*inherited_state);
			unsigned j;
			for (j=0; j<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive && inherited_state->m_lights[j]; ++j)
			{
				Check_Object(inherited_state->m_lights[j]);
				mixed.m_lights[j] = inherited_state->m_lights[j];
			}
			if(j<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
			{
				mixed.m_lights[j] = NULL;
			}

			Stuff::Scalar fade = FadeColor.alpha;
			if (distance < entry->m_fadeInSquared)
			{
				Stuff::Scalar temp =
					(distance - entry->m_nearSquared) / (entry->m_fadeInSquared - entry->m_nearSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}
			else if (distance > entry->m_fadeOutSquared)
			{
				Stuff::Scalar temp =
					(entry->m_farSquared - distance) / (entry->m_farSquared - entry->m_fadeOutSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}

			//
			//----------------------------
			// Set up the draw information
			//----------------------------
			//
			MidLevelRenderer::DrawShapeInformation info;
			Check_Object(m_MLRShapes[i]);
			info.shape = m_MLRShapes[i];
			info.clippingFlags.SetClippingState(culling_state);

			//
			//-------------------------------------------------
			// If we are far enough away, do the aligning thing
			//-------------------------------------------------
			//
			Stuff::LinearMatrix4D new_local_to_world, new_world_to_local;
			if (i>=m_startAligningAt)
			{
				switch (m_alignType)
				{
				case AlignXFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::X_Axis, -1);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				case AlignYFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, -1);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				case AlignXFlag|AlignYFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, Stuff::X_Axis);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				default:
					STOP(("Bad align type"));
					break;
				}
			}

			//
			//------------------------------------------------
			// Otherwise, use the data burned into the element
			//------------------------------------------------
			//
			else
			{
				Check_Object(&m_localToWorld);
				info.shapeToWorld = &m_localToWorld;
				Check_Object(&m_worldToLocal);
				info.worldToShape = &m_worldToLocal;
			}

			info.state = mixed.GetMLRState();
			#if defined(LAB_ONLY)
				if (LODElement::s_ShadeLODs && i<ELEMENTS(LODElement::s_Shades))
					info.paintMeColor = &LODElement::s_Shades[i];
				else
			#endif
			info.paintMeColor = &FadeColor;
			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::Tree::Inherited");
			Start_Timer(Draw_Shapes);
			clipper->DrawShape(&info);
			Stop_Timer(Draw_Shapes);
			Start_Timer(Graph_Traversal);

			FadeColor.alpha = fade;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ElementRenderer::TreeElement::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 (ShapeElement::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

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + LODElement::s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//-------------------------------------------------
	// Evaluate each LOD to see if it needs to be drawn
	//-------------------------------------------------
	//
	for (int i=m_ranges.GetLength()-1; i>=0; --i)
	{
		Entry *entry = &m_ranges[i];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{

			//
			//-----------------------------------------
			// Set the fade color if we are in the band
			//-----------------------------------------
			//
			Stuff::Scalar fade = FadeColor.alpha;
			if (distance < entry->m_fadeInSquared)
			{
				Stuff::Scalar temp =
					(distance - entry->m_nearSquared) / (entry->m_fadeInSquared - entry->m_nearSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}
			else if (distance > entry->m_fadeOutSquared)
			{
				Stuff::Scalar temp =
					(entry->m_farSquared - distance) / (entry->m_farSquared - entry->m_fadeOutSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}

			//
			//----------------------------
			// Set up the draw information
			//----------------------------
			//
			MidLevelRenderer::DrawShapeInformation info;
			Check_Object(m_MLRShapes[i]);
			info.shape = m_MLRShapes[i];
			info.clippingFlags.SetClippingState(culling_state);

			//
			//-------------------------------------------------
			// If we are far enough away, do the aligning thing
			//-------------------------------------------------
			//
			Stuff::LinearMatrix4D new_local_to_world, new_world_to_local;
			if (i>=m_startAligningAt)
			{
				switch (m_alignType)
				{
				case AlignXFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::X_Axis, -1);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				case AlignYFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, -1);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				case AlignXFlag|AlignYFlag:
					{
						Check_Object(m_parent);
						Stuff::Point3D world_camera(camera->GetLocalToWorld());
						Stuff::Point3D parent_camera;
						parent_camera.MultiplyByInverse(
							world_camera,
							m_parent->GetLocalToWorld()
						);
						Stuff::Vector3D camera_direction;
						Stuff::Point3D local_origin(m_newLocalToParent);
						camera_direction.Subtract(parent_camera, local_origin);
						Stuff::LinearMatrix4D new_local_to_parent(m_newLocalToParent);
						new_local_to_parent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, Stuff::X_Axis);
						new_local_to_world.Multiply(new_local_to_parent, m_parent->GetLocalToWorld());
						Check_Object(&m_localToWorld);
						info.shapeToWorld = &new_local_to_world;
						new_world_to_local.Invert(new_local_to_world);
						info.worldToShape = &new_world_to_local;
					}
					break;

				default:
					STOP(("Bad align type"));
					break;
				}
			}

			//
			//------------------------------------------------
			// Otherwise, use the data burned into the element
			//------------------------------------------------
			//
			else
			{
				Check_Object(&m_localToWorld);
				info.shapeToWorld = &m_localToWorld;
				Check_Object(&m_worldToLocal);
				info.worldToShape = &m_worldToLocal;
			}

			info.state = mixed.GetMLRState();
			#if defined(LAB_ONLY)
				if (LODElement::s_ShadeLODs && i<ELEMENTS(LODElement::s_Shades))
					info.paintMeColor = &LODElement::s_Shades[i];
				else
			#endif
			info.paintMeColor = &FadeColor;
			LightList culled_lights;
			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::Tree::Overridden");
			Start_Timer(Draw_Shapes);
			clipper->DrawShape(&info);
			Stop_Timer(Draw_Shapes);
			Start_Timer(Graph_Traversal);

			FadeColor.alpha = fade;
		}
	}
}

extern DWORD Disable_Lights;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::TreeElement::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;

	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 >= ShapeElement::s_MaxLightsPerShape)
			break;
	}
	Verify(new_count <= MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
	return new_count;
}

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

	if (m_MLRShapes.GetLength() > 0)
	{
		Check_Object(m_MLRShapes[0]);
		return m_MLRShapes[0]->GetNumPrimitives();
	}
	return 0;
}

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

	for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
	{
		Check_Object(m_MLRShapes[i]);
		Verify(m_MLRShapes[i]->GetReferenceCount() == 1);
		m_MLRShapes[i]->CleanMe();
	}
}

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

	for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
	{
		Check_Object(m_MLRShapes[i]);
		Verify(m_MLRShapes[i]->GetReferenceCount() == 1);
		m_MLRShapes[i]->HurtMe(damage_spot, radius);
	}
}

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

	for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
	{
		Check_Object(m_MLRShapes[i]);
		Verify(m_MLRShapes[i]->GetReferenceCount() == 1);
		m_MLRShapes[i]->HurtMe(damage_spot, radius, texture);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::TreeElement::CastShadow(
		const Stuff::LinearMatrix4D &shadow_to_world,
		const Stuff::UnitVector3D &sun_in_world,
		Stuff::Scalar radius
	)
{
	Check_Object(this);

	if (!IsShadowed())
		return;
	MidLevelRenderer::MLRShape *mlr_shape;
	const Entry *entry;
	Verify(GetSize()>0);
	GetLOD(static_cast<WORD>(GetSize()-1), &mlr_shape, &entry);
	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);
	mlr_shape->DetachReference();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::TreeElement::MakeFootStep(
		MidLevelRenderer::MLRShape *shape,
		const Stuff::LinearMatrix4D &foot,
		Stuff::Scalar radius,
		MidLevelRenderer::MLRTexture *tex
	)
{
	Check_Object(this);

	MidLevelRenderer::MLRShape *mlr_shape;
	const Entry *entry;

	Verify(GetSize()>0);
	GetLOD(static_cast<WORD>(GetSize()-1), &mlr_shape, &entry);

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MidLevelRenderer::MLRShape*
	ElementRenderer::TreeElement::GetMLRShape(ElementRenderer::CameraElement* cameraElement)
{
	Check_Object(this); 

	unsigned short lod = 0;
	if (cameraElement)
	{
		Stuff::Point3D camera_origin(cameraElement->GetLocalToWorld());
		Stuff::Point3D lod_origin(GetLocalToWorld());
		camera_origin -= lod_origin;
		Stuff::Scalar distance = camera_origin.GetLengthSquared() + LODElement::s_Offset;
		Min_Clamp(distance, 0.0f);
		
		//
		//---------------------------------------------------
		// Search the lod entries looking for the first match
		//---------------------------------------------------
		//
		for (WORD i=0; i<m_MLRShapes.GetLength(); ++i)
		{
			Entry *entry = &m_ranges[i];
			if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
			{
				lod = i;
				break;
			}
		}
	}
	
	MidLevelRenderer::MLRShape *mlr_shape;
	const Entry* ranges;

	Verify(lod < GetSize());
	GetLOD(lod, &mlr_shape, &ranges);
	mlr_shape->DetachReference();

	return mlr_shape;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	Returns array of vertex coordinates and number of vertices
void
	ElementRenderer::TreeElement::GetCoordData(
		Stuff::DynamicArrayOf<Stuff::Point3D> *array)
{
	Check_Object(this);

	//
	//-----------------------------------
	// First count the number of vertices
	//-----------------------------------
	//
	int total=0;
	int meshes = m_MLRShapes.GetLength();
	int m;
	for(m=0; m<meshes; m++)
	{
		int ns = m_MLRShapes[m]->GetNum();
		for (int n =0;n<ns;n++)
		{
			MidLevelRenderer::MLRPrimitiveBase *mesh = m_MLRShapes[m]->Find(n);
			Check_Object(mesh);
			const Stuff::Point3D *verts;
			int count;
			mesh->GetCoordData(&verts, &count);
			total += count;
		}
	}

	//
	//------------------------
	// Now allocate the memory
	//------------------------
	//
	array->SetLength(total);
	total = 0;
	for(m=0; m<meshes; m++)
	{
		int ns = m_MLRShapes[m]->GetNum();
		for (int n =0;n<ns;n++)
		{
			MidLevelRenderer::MLRPrimitiveBase *mesh = m_MLRShapes[m]->Find(n);
			Check_Object(mesh);
			const Stuff::Point3D *verts;
			int count;
			mesh->GetCoordData(&verts, &count);

			for (int v=0; v<count; ++v)
			{
				(*array)[total+v]=verts[v];
			}
			total += count;
		}
	}
}

